1.husky 源码浅析
2.这些hook更优雅的超级超级管理你的状态
3.JS逆向快速定位关键点之9大通用hook脚本
4.VirtualAPP源码解析-Native Hook技术
5.C# Hook原理及EasyHook简易教程
husky 源码浅析
解析 Husky 源码:揭示 Git 钩子的奥秘
前言
在探索 Husky 的工作原理之前,让我们先回顾一下自定义 Git Hook 的源码源码概念。通过 Husky,超级超级我们能够实现对 Git 钩子的源码源码指定目录控制,灵活地执行预先定义的超级超级命令。本篇文章将带领大家深入 Husky 的源码源码风暴幻想2源码源码,揭示其工作流程和使用 Node.js 编写 CLI 工具的超级超级要点。Husky 工作流程
从 Husky 的源码源码安装流程入手,我们能够直观地理解其工作原理。超级超级主要步骤如下:执行 `npx husky install`。源码源码
通过 Git 命令,超级超级将 hooks 目录指向 Husky 提供的源码源码目录。
确保新拉取的超级超级仓库在执行 `install` 后自动调整 Git hook 目录,以保持一致性。源码源码
在这一过程中,超级超级Husky 通过巧妙地添加 npm 钩子,确保了新仓库在安装完成后能够自动配置 Git 钩子路径,实现了跨平台的统一性。源码浅析
bin.ts
bin.ts 文件简洁明了,核心在于模块导入语法和 Node.js CLI 工具的实现。它支持了导入模块的两种方式,并解释了在 TypeScript 中如何灵活使用它们。npm 中的可执行文件
通过配置 package.json 的 `bin` 字段,我们可以将任意脚本或工具作为 CLI 工具进行全局安装,以便在命令行中直接调用。Husky 利用这一特性,为用户提供了一个简洁的安装流程和便捷的调用方式。获取命令行参数
在 Node.js 中,`process.argv` 提供了获取命令行参数的便捷方式。通过解析这个数组,我们可以轻松获取用户传递的参数,实现命令与功能的阿里通源码对应。index.ts
核心逻辑在于安装、配置和卸载 Git 钩子的函数。Husky 的代码结构清晰,易于理解。其中,`core.hooksPath` 的配置和权限设置(如 `mode 0o`)是关键步骤,确保了 Git 钩子的执行权限和统一性。husky.sh
作为初始化脚本,husky.sh 执行了一系列环境配置和日志输出操作。其重点在于根据不同 Shell 环境(如 Zsh)进行适配性处理,确保 Husky 在各类环境中都能稳定运行。结语
Husky 的实现通过 `git config core.hooksPath` 和 `npm prepare` 钩子的巧妙结合,不仅简化了 Git 钩子的配置流程,还提升了代码的可移植性和一致性。使用 Husky,开发者能够更灵活地管理 Git 钩子,提升项目的自动化程度。这些hook更优雅的管理你的状态
本文是深入浅出ahooks源码系列文章的第十二篇,这个系列的目标主要有以下几点:加深对Reacthooks的理解。
学习如何抽象自定义hooks。构建属于自己的Reacthooks工具库。
培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。
今天我们来聊聊ahooks中那些可以帮助我们更优雅管理我们state(状态)的那些hook。一些比较特殊的,比如cookie/localStorage/sessionStorage,useUrlState等,我们已经单独拿出来细讲了,感兴趣可以看看笔者的历史文章。
useSetState管理object类型state的Hooks,用法与class组件的腾讯分分源码this.setState基本一致。
先来了解一下可变数据和不可变数据的含义和区别如下:
可变数据(mutable)即一个数据被创建之后,可以随时进行修改,修改之后会影响到原值。
不可变数据(Immutable)就是一旦创建,就不能再被更改的数据。对Immutable对象的任何修改或添加删除操作都会返回一个新的Immutable对象。
我们知道,ReactFunctionComponents中的State是不可变数据。所以我们经常需要写类似如下的代码:
setObj((prev)=>({ ...prev,name:'Gopal',others:{ ...prev.others,age:'',}}));通过useSetState,可以省去对象扩展运算符操作这个步骤,即:
setObj((prev)=>({ name:'Gopal',others:{ age:'',}}));其内部实现也比较简单,如下所示:
调用设置值方法的时候,会根据传入的值是否为函数。如果是函数,则入参为旧状态,输出新的状态。否则直接作为新状态。这个符合setState的使用方法。
使用对象拓展运算符,返回新的对象,保证原有数据不可变。
constuseSetState=<SextendsRecord<string,any>>(initialState:S|(()=>S),):[S,SetState<S>]=>{ const[state,setState]=useState<S>(initialState);//合并操作,并返回一个全新的值constsetMergeState=useCallback((patch)=>{ setState((prevState)=>{ //新状态constnewState=isFunction(patch)?patch(prevState):patch;//也可以通过类似Object.assign的方式合并//对象拓展运算符,返回新的对象,保证原有数据不可变returnnewState?{ ...prevState,...newState}:prevState;});},[]);return[state,setMergeState];};可以看到,其实就是将对象拓展运算符的操作封装到内部。
还有其他更优雅的方式?我们可以使用use-immer
useImmer(initialState)非常类似于useState。该函数返回一个元组,元组的第一个值是当前状态,第二个是updater函数,它接受一个immerproducer函数或一个值作为参数。无极4源码
使用如下:
const[person,updatePerson]=useImmer({ name:"Michel",age:});functionupdateName(name){ updatePerson(draft=>{ draft.name=name;});}functionbecomeOlder(){ updatePerson(draft=>{ draft.age++;});}当向更新函数传递一个函数的时候,draft参数可以自由地改变,直到producer函数结束,所做的改变将是不可变的,并成为下一个状态。这更符合我们的使用习惯,可以通过draft.xx.yy的方式更新我们对象的值。
useBoolean和useToggle这两个都是特殊情况下的值管理。
useBoolean,优雅的管理boolean状态的Hook。
useToggle,用于在两个状态值间切换的Hook。
实际上,useBoolean又是useToggle的一个特殊使用场景。
先看useToggle。
这里使用了typescript函数重载声明入参和出参类型,根据不同的入参会返回不同的结果。比如第一个入参为boolean布尔值,则返回一个元组,第一项为boolean值,第二个为更新函数。优先级从上到下依次变低。
入参可能有两个值,第一个为默认值(认为是左值),第二个是取反之后的值(认为是右值),可以不传,不传的时候,则直接根据默认值取反!defaultValue。
toggle函数。切换值,也就是溯源码材质上面的左值和右值的转换。
set。直接设置值。
setLeft。设置默认值(左值)。
setRight。如果传入了reverseValue,则设置为reverseValue。否则设置为defautValue的取反值。
//TS函数重载的使用functionuseToggle<T=boolean>():[boolean,Actions<T>];functionuseToggle<T>(defaultValue:T):[T,Actions<T>];functionuseToggle<T,U>(defaultValue:T,reverseValue:U):[T|U,Actions<T|U>];functionuseToggle<D,R>(//默认值defaultValue:D=falseasunknownasD,//取反reverseValue?:R,){ const[state,setState]=useState<D|R>(defaultValue);constactions=useMemo(()=>{ constreverseValueOrigin=(reverseValue===undefined?!defaultValue:reverseValue)asD|R;//切换stateconsttoggle=()=>setState((s)=>(s===defaultValue?reverseValueOrigin:defaultValue));//修改stateconstset=(value:D|R)=>setState(value);//设置为defaultValueconstsetLeft=()=>setState(defaultValue);//如果传入了reverseValue,则设置为reverseValue。否则设置为defautValue的反值constsetRight=()=>setState(reverseValueOrigin);return{ toggle,set,setLeft,setRight,};//useToggleignorevaluechange//},[defaultValue,reverseValue]);},[]);return[state,actions];}而useBoolean是对useToggle的一个使用。如下,比较简单,不细说
exportdefaultfunctionuseBoolean(defaultValue=false):[boolean,Actions]{ const[state,{ toggle,set}]=useToggle(defaultValue);constactions:Actions=useMemo(()=>{ constsetTrue=()=>set(true);constsetFalse=()=>set(false);return{ toggle,set:(v)=>set(!!v),setTrue,setFalse,};},[]);return[state,actions];}usePrevious保存上一次状态的Hook。
其原理,是每次状态变更的时候,比较值有没有发生变化,变更状态:
维护两个状态prevRef(保存上一次的状态)和curRef(保存当前状态)。
状态变更的时候,使用shouldUpdate判断是否发生变化,默认通过Object.is判断。开发者可以自定义shouldUpdate函数,并决定什么时候记录上一次状态。
状态发生变化,更新prevRef的值为上一个curRef,并更新curRef为当前的状态。
constdefaultShouldUpdate=<T>(a?:T,b?:T)=>!Object.is(a,b);functionusePrevious<T>(state:T,shouldUpdate:ShouldUpdateFunc<T>=defaultShouldUpdate,):T|undefined{ //使用了useRef的特性,一直保持引用不变//保存上一次值constprevRef=useRef<T>();//当前值constcurRef=useRef<T>();//自定义是否更新上一次的值if(shouldUpdate(curRef.current,state)){ prevRef.current=curRef.current;curRef.current=state;}returnprevRef.current;}useRafState只在requestAnimationFramecallback时更新state,一般用于性能优化。
window.requestAnimationFrame()告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
假如你的操作是比较频繁的,就可以通过这个hook进行性能优化。
重点看setRafState方法,它执行的时候,会取消上一次的setRafState操作。重新通过requestAnimationFrame去控制setState的执行时机。
另外在页面卸载的时候,会直接取消操作,避免内存泄露。
functionuseRafState<S>(initialState?:S|(()=>S)){ constref=useRef(0);const[state,setState]=useState(initialState);constsetRafState=useCallback((value:S|((prevState:S)=>S))=>{ cancelAnimationFrame(ref.current);ref.current=requestAnimationFrame(()=>{ setState(value);});},[]);//unMount的时候,去除监听useUnmount(()=>{ cancelAnimationFrame(ref.current);});return[state,setRafState]asconst;}useSafeState用法与React.useState完全一样,但是在组件卸载后异步回调内的setState不再执行,避免因组件卸载后更新状态而导致的内存泄漏。
代码如下:
在更新的时候,通过useUnmountedRef判断如果组件卸载,则停止更新。
functionuseSafeState<S>(initialState?:S|(()=>S)){ //判断是否卸载constunmountedRef=useUnmountedRef();const[state,setState]=useState(initialState);constsetCurrentState=useCallback((currentState)=>{ //如果组件卸载,则停止更新if(unmountedRef.current)return;setState(currentState);},[]);return[state,setCurrentState]asconst;}useUnmountedRef这个我们之前提过,简单回顾下,其实就是在hook的返回值中标记组件为已卸载。
constuseUnmountedRef=()=>{ constunmountedRef=useRef(false);useEffect(()=>{ unmountedRef.current=false;//如果已经卸载,则会执行return中的逻辑return()=>{ unmountedRef.current=true;};},[]);returnunmountedRef;};useGetState给React.useState增加了一个getter方法,以获取当前最新值。
其实现如下:
其实就是通过useRef记录最新的state的值,并暴露一个getState方法获取到最新的。
setObj((prev)=>({ name:'Gopal',others:{ age:'',}}));0这在某一些情况下,可以避免React的闭包陷阱。如官网例子:
setObj((prev)=>({ name:'Gopal',others:{ age:'',}}));1假如这里不使用getCount(),而是直接使用count,是获取不到最新的值的。
总结与思考React的functionComponent的状态管理还是比较灵活,我们可以针对一些场景进行封装和优化,从而更优雅的管理我们的state状态,希望ahooks这些封装能对你有所帮助。
原文:/post/JS逆向快速定位关键点之9大通用hook脚本
在JavaScript逆向工程中,快速定位关键点是至关重要的。这里有九种通用的hook脚本策略,帮助我们深入分析代码行为: 首先,Cookie Hook帮助我们定位Cookie中与关键字"v"相关的参数生成位置,一旦匹配,就会自动设置断点。 对于HTTP头部参数,header参数Hook可以用于跟踪请求中特定字段的变化。 通过简单地在代码中插入debugger,Hook过debugger方法为我们提供了一个直观的检查点,便于调试。 URL Hook专注于URL中的关键参数,当遇到包含"login"的请求时,会自动设立断点,便于追踪网络请求。 在处理加密的站点时,hook JSON.stringify和 JSON.parse可以让我们洞察数据在字符串和对象间的转换过程。 JavaScript的eval 功能,通过提前设置的hook,hook eval可以记录下所有动态执行的JS代码,便于分析。 同样,hook Function会捕获所有函数的执行,让我们看到将要运行的JS源码。最后,别忘了防范反调试,确保在执行过程中不会被检测到,这是逆向工程中必不可少的通用反调试技巧。
这些脚本工具为深入JavaScript代码提供了强大的支持,帮助我们轻松定位关键逻辑点。
VirtualAPP源码解析-Native Hook技术
Native Hook技术在VirtualAPP中的应用背景在于虚拟APP的文件访问重定向。VirtualAPP作为子进程启动一个虚拟APP时,文件存储路径会默认指向VirtaulAPP的data目录。这可能导致文件访问冲突,且无法实现APP间的隔离。VirtualAPP通过Native Hook技术解决了这个问题,让每个APP有独立的文件存储路径。
实现原理关键在于VClientImpl的startIOUniformer方法,通过进行存储路径映射,将子进程访问的目录路径转换为虚拟app路径。这个过程通过调用IOUniformer.cpp的startUniformer方法实现。我们知道Android系统基于Linux内核,文件读写操作通过库函数进行系统调用。因此,Native Hook技术实现方式是替换libc库函数的方法,将输入参数替换为虚拟app路径,从而实现文件访问路径的重定向。
要确定需要hook的函数,开发者需要查看libc源码。Native Hook技术有PLT Hook与Inline Hook两种实现方式。PLT Hook主要通过替换程序链接表中的地址,而Inline Hook则直接修改汇编代码,实现更广泛的场景与更强的能力。虚拟app使用的第三方开源项目Cydia Substrate实现了Inline Hook方案,而爱奇艺开源的xHook则采用了PLT Hook方案。虚拟app通过宏定义灵活运用这两种Hook方案,实现对libc库函数的替换。
Native Hook技术的实现过程涉及到so动态链接、ELF文件格式、汇编指令等知识,其具体步骤包括定义Hook调用和替换方法。例如,通过HOOK_SYMBOL宏定义函数指针,HOOK_DEF宏定义替换函数,最终通过hook_function方法实现Hook操作。MSHookFunction函数即为Cydia Substrate提供的Hook能力。
学习Native Hook技术需要逐步积累,理解其原理和实现过程需要时间和实践。后续文章将深入探讨MSHookFunction的具体实现原理,进一步帮助读者掌握Native Hook技术。
C# Hook原理及EasyHook简易教程
C#通过调用Windows API和利用EasyHook库,实现了对Windows平台消息处理机制的扩展,允许开发者拦截和处理特定窗口的消息。下面是一个直观的教程,展示如何在C#中利用EasyHook进行Hook操作。
C#中,尽管不能直接操作内存,但可通过调用Windows API来实现Hook功能。例如,通过SetWindowsHookEx、UnhookWindowsHookEx和CallNextHookEx等函数,安装、执行和卸载Hook子程,从而在消息到达目标窗口处理函数前进行拦截。
使用EasyHook,开发者可以绕过C#对Windows API操作的限制。首先,创建一个WinForm项目,引用EasyHook库。在主窗体中,通过获取进程ID,判断系统位数,然后将自定义DLL注册到GAC以便在目标进程中调用。接着,使用EasyHook的RemoteHooking.Inject方法注入DLL,定义Hook函数,如修改MessageBox的内容和标题。
在实际操作中,通过LocalHook函数获取MessageBox的地址并创建本地钩子,Hook成功后,原有的MessageBox功能会被修改。EasyHook的易用性和跨平台支持,使得C# Hook变得更为可行和便捷。
虽然EasyHook提供了便利,但中文资料相对匮乏,学习过程中可能存在挑战。作者鼓励大家共同探讨和分享Hook经验,如果有任何疑问或建议,可以在评论区交流。源代码和更多详细教程可参考作者的博客文章:C# Hook原理及EasyHook简易教程 - Wackysoft - 博客园。