精华内容
下载资源
问答
  • 自己经常没事做做单机游戏的作弊软件玩,经常遇到需要写hook的时候 ,于是乎就自己写了一个小巧的hook类库, 使用了beaengine的汇编引擎,如果是用来做系统apihook 也可以做到 只是没有detour那么简单无脑好用,我...

    自己经常没事做做单机游戏的作弊软件玩,经常遇到需要写hook的时候 ,于是乎就自己写了一个小巧的hook类库,

    使用了beaengine的汇编引擎,如果是用来做系统apihook 也可以做到 只是没有detour那么简单无脑好用,我主要是用来做一些inline hook ,

    监控/修改某些寄存器的值,刚刚随手写完了 就想着发上来吧,以后有需要也可以自己慢慢的拓展,目前只支持x86 。


     void __stdcall myfunction(hook_context &context)
    {
    	 context.process_orignal_function = 0;
    	 context.eax = mini_hook::get_instance()->get_orignal_function<decltype(MessageBoxA)*>("test")(0,"test","test",MB_OK);
    }
    int main()
    {
     
    	mini_hook::get_instance()->hook_on((PVOID)MessageBoxA, (PVOID)myfunction,"test",4);
    	MessageBoxA(0, "123456", "123456", 0);
    	mini_hook::get_instance()->hook_detach("test");
    	MessageBoxA(0, "123456", "123456", 0);
    
        return 0;
    }
    



    mini_hook.h

    #pragma once
    #include <windows.h>
    #include <map>
    
    struct hook_context
    {
    	DWORD eax, ebx, ecx, edx, edi, esi, ebp, esp;
    	DWORD args_counts; //占坑保留...没用到
    	DWORD process_orignal_function;
    };
    struct hook_data
    {
    	PVOID hook_address;
    	PVOID user_callback;
    	DWORD bad_code_len;
    	PVOID temp_function_address;
    	PVOID orignal_funtcion_address;  // 这个原始函数调用目前只适用于hook在函数头部
    	byte  bad_code[0x10] = { 0 };
    };
    class mini_hook
    {
    private:
    	mini_hook();
    public:
    	static  mini_hook *	get_instance();
    
    public:
    	~mini_hook();
    	 void				destroy();
    	 hook_data*			get_data(const char *flag);
    	 hook_data*			get_data(PVOID hooked_address);
    	 void				hook_on(PVOID hook_address, PVOID user_callback,const char * flag,int argsCounts=0);
    	 void				hook_detach(const char* flag);
    	 void				detach_all();
    public:
    	 template<typename T>			
    		 T get_orignal_function(const char * flag)
    	 {
    			 return reinterpret_cast<T>(m_hookinfo[flag].orignal_funtcion_address);
    	 }
    protected:
    	DWORD				calc_badecode_len(PVOID address);
    	void				write_jmp(DWORD address, DWORD dest);
    	DWORD				read_jmp(PVOID address);
    protected:
    	static mini_hook * m_pthis;
    	std::map<  const char *, hook_data> m_hookinfo;
    };
    



    mini_hook.cpp

    #include "stdafx.h"
    #include "mini_hook.h"
    #include "mini_tool.h"
    #include "beaengine-win32/headers/BeaEngine.h"  
    
    #pragma comment(lib, "beaengine-win32\\Win32\\Lib\\BeaEngine.lib")  
    
    #define ASM_CODE_LEN 0x99
    #define ASM_USERCALLBACK_OFFSET 0x3d
    #define ASM_NOT_PROCESS_OFFSET 0x73
    mini_hook::mini_hook()
    {
    }
    
    
    mini_hook::~mini_hook()
    {
    }
          
    /* hook MsgBoxA生成的汇编代码 代码可以简化挺多的,汇编比较差吧我...
    $ ==>    >  83EC 30         sub esp,0x30
    $+3      >  890424          mov dword ptr ss:[esp],eax
    $+6      >  895C24 04       mov dword ptr ss:[esp+0x4],ebx
    $+A      >  894C24 08       mov dword ptr ss:[esp+0x8],ecx
    $+E      >  895424 0C       mov dword ptr ss:[esp+0xC],edx
    $+12     >  897C24 10       mov dword ptr ss:[esp+0x10],edi
    $+16     >  897424 14       mov dword ptr ss:[esp+0x14],esi
    $+1A     >  896C24 18       mov dword ptr ss:[esp+0x18],ebp
    $+1E     >  896424 1C       mov dword ptr ss:[esp+0x1C],esp
    $+22     >  50              push eax
    $+23     >  8B4424 20       mov eax,dword ptr ss:[esp+0x20]
    $+27     >  83C0 30         add eax,0x30
    $+2A     >  894424 20       mov dword ptr ss:[esp+0x20],eax
    $+2E     >  B8 01000000     mov eax,0x1
    $+33     >  894424 28       mov dword ptr ss:[esp+0x28],eax
    $+37     >  8BC4            mov eax,esp
    $+39     >  83C0 04         add eax,0x4
    $+3C     >  50              push eax
    $+3D     >  E8 C2C4F0FF     call minihook.000EC504
    $+42     >  8BC4            mov eax,esp
    $+44     >  8B4424 28       mov eax,dword ptr ss:[esp+0x28]
    $+48     >  83F8 01         cmp eax,0x1
    $+4B     >  74 29           je short 001E0076
    $+4D     >  58              pop eax
    $+4E     >  8B4424 20       mov eax,dword ptr ss:[esp+0x20]
    $+52     >  8B0424          mov eax,dword ptr ss:[esp]
    $+55     >  8B5C24 04       mov ebx,dword ptr ss:[esp+0x4]
    $+59     >  8B4C24 08       mov ecx,dword ptr ss:[esp+0x8]
    $+5D     >  8B5424 0C       mov edx,dword ptr ss:[esp+0xC]
    $+61     >  8B7C24 10       mov edi,dword ptr ss:[esp+0x10]
    $+65     >  8B7424 14       mov esi,dword ptr ss:[esp+0x14]
    $+69     >  8B6C24 18       mov ebp,dword ptr ss:[esp+0x18]
    $+6D     >  83C4 30         add esp,0x30
    $+70     >  90              nop
    $+71     >  90              nop
    $+72     >  90              nop
    $+73     >  C2 1000         retn 0x10
    $+76     >  58              pop eax
    $+77     >  8B0424          mov eax,dword ptr ss:[esp]
    $+7A     >  8B5C24 04       mov ebx,dword ptr ss:[esp+0x4]
    $+7E     >  8B4C24 08       mov ecx,dword ptr ss:[esp+0x8]
    $+82     >  8B5424 0C       mov edx,dword ptr ss:[esp+0xC]
    $+86     >  8B7C24 10       mov edi,dword ptr ss:[esp+0x10]
    $+8A     >  8B7424 14       mov esi,dword ptr ss:[esp+0x14]
    $+8E     >  8B6C24 18       mov ebp,dword ptr ss:[esp+0x18]
    $+92     >  90              nop
    $+93     >  90              nop
    $+94     >  90              nop
    $+95     >  90              nop
    $+96     >  83C4 30         add esp,0x30
    $+99     >  8BFF            mov edi,edi
    $+9B     >  55              push ebp
    $+9C     >  8BEC            mov ebp,esp
    $+9E     >- E9 10FDEE76     jmp user32.770CFDB3
    
    
    */
    byte asm_code[] = { 0x83,0xEC,0x30,0x89,0x04,0x24,0x89,0x5C,0x24,0x04,0x89,0x4C,0x24,0x08,0x89,0x54,
    0x24,0x0C,0x89,0x7C,0x24,0x10,0x89,0x74,0x24,0x14,0x89,0x6C,0x24,0x18,0x89,0x64,
    0x24,0x1C,0x50,0x8B,0x44,0x24,0x20,0x83,0xC0,0x30,0x89,0x44,0x24,0x20,0xB8,0x01,
    0x00,0x00,0x00,0x89,0x44,0x24,0x28,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xE8,0xB0,0xEF,
    0x00,0x00,0x8B,0xC4,0x8B,0x44,0x24,0x28,0x83,0xF8,0x01,0x74,0x29,0x58,0x8B,0x44,
    0x24,0x20,0x8B,0x04,0x24,0x8B,
    0x5C,0x24,0x04,0x8B,0x4C,0x24,0x08,0x8B,0x54,0x24,0x0C,0x8B,0x7C,0x24,0x10,0x8B,
    0x74,0x24,0x14,0x8B,0x6C,0x24,0x18,0x83,0xC4,0x30,0x90,0x90,0x90,0x90,0x90,0xC3,0x58,0x8B,0x04,0x24,0x8B,
    0x5C,0x24,0x04,0x8B,0x4C,0x24,0x08,0x8B,0x54,0x24,0x0C,0x8B,0x7C,0x24,0x10,0x8B,
    0x74,0x24,0x14,0x8B,0x6C,0x24,0x18,0x90,0x90,0x90,0x90,0x83,0xC4,0x30 };
    
    
    
    mini_hook* mini_hook::m_pthis = nullptr;
    
    mini_hook * mini_hook::get_instance()
    {
    	if (!m_pthis)
    	{
    		m_pthis = new mini_hook;
    	}
    	
    	return m_pthis;
    }
    
    void mini_hook::destroy()
    {
    	if (m_pthis)
    	{
    		detach_all();
    		delete m_pthis;
    	}
    	m_pthis = nullptr;
    }
    
    hook_data * mini_hook::get_data(PVOID hooked_address)
    {
    	auto iter = m_hookinfo.begin();
    	while (iter != m_hookinfo.end())
    	{
    		if (iter->second.hook_address == hooked_address)
    		{
    			return reinterpret_cast<hook_data*>(&iter->second);
    		}
    		++iter;
    	}
    
    	return nullptr;
    }
    
    hook_data * mini_hook::get_data(const char *flag)
    {
    	return reinterpret_cast<hook_data*>(&m_hookinfo[flag]);
    }
    
    DWORD mini_hook::calc_badecode_len(PVOID address)
    {
    	DISASM ASM;
    	memset(&ASM, 0, sizeof(DISASM));
    
    	ASM.EIP = reinterpret_cast<UIntPtr>(address);
    
    	int bad_code_len = 0;
    
    	while (bad_code_len < 5)
    	{
    		auto len = Disasm(&ASM);
    		if (len != UNKNOWN_OPCODE)
    		{
    			bad_code_len += len;
    			ASM.EIP += len;
    		}
    		else
    		{
    			++bad_code_len;
    			++ASM.EIP;
    		}
    	}
    
    	return bad_code_len;
    }
    
    void mini_hook::hook_on(PVOID hook_address, PVOID user_callback, const char * flag,int argsCounts)
    {
    	hook_data data;
    	data.hook_address = hook_address;
    	data.user_callback = user_callback;
    
    	DWORD bade_code_len = calc_badecode_len(hook_address);
    	DWORD jump_ret = reinterpret_cast<DWORD>(hook_address) + bade_code_len;
    	data.bad_code_len = bade_code_len;
    	memcpy(data.bad_code, hook_address, bade_code_len);
    
    	data.temp_function_address =  VirtualAlloc(NULL, ASM_CODE_LEN + 0x20, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    
    	byte jump_code[5] = { 0 };
    	jump_code[0] = 0xe9;
    	*reinterpret_cast<DWORD*>(jump_code + 1) = reinterpret_cast<DWORD>(data.temp_function_address)
    		- reinterpret_cast<DWORD>(hook_address)
    		- 5;
    
    	if (IsBadReadPtr(data.temp_function_address, 4))
    	{
    		return;
    	}
    	
    	memcpy(data.temp_function_address, asm_code, ASM_CODE_LEN);
    
    	write_jmp(reinterpret_cast<DWORD>(data.temp_function_address) + ASM_USERCALLBACK_OFFSET, 
    		reinterpret_cast<DWORD>(user_callback));
    
    	memcpy(reinterpret_cast<PVOID>(reinterpret_cast<DWORD>(data.temp_function_address) + ASM_CODE_LEN), 
    		data.bad_code, bade_code_len);
    
    	mini_tool::mini_safe_write(reinterpret_cast<PVOID>(reinterpret_cast<DWORD>(data.temp_function_address) + ASM_CODE_LEN+bade_code_len ),
    		jump_code, 1);
    
    	write_jmp(reinterpret_cast<DWORD>(data.temp_function_address) + ASM_CODE_LEN + bade_code_len, jump_ret);
    
    
    	DISASM ASM;
    	memset(&ASM, 0, sizeof(DISASM));
    
    	ASM.EIP = reinterpret_cast<DWORD>(data.temp_function_address) + ASM_CODE_LEN;
    	int i = 0;
    	while (i < bade_code_len)
    	{
    		auto len = Disasm(&ASM);
    		if (len != UNKNOWN_OPCODE)
    		{
    			
    			if ((strstr(ASM.CompleteInstr, "call") >= 0 && READ_BYTE_WITH_OFFSET(data.temp_function_address, ASM_CODE_LEN + i) == 0xe8) ||
    				(strstr(ASM.CompleteInstr, "jmp") >= 0 && READ_BYTE_WITH_OFFSET(data.temp_function_address, ASM_CODE_LEN + i) == 0xe9))
    			{
    				auto orignal_address = read_jmp(reinterpret_cast<PVOID>(reinterpret_cast<DWORD>(hook_address) + i));
    				write_jmp(reinterpret_cast<DWORD>(data.temp_function_address) + ASM_CODE_LEN + i, orignal_address);
    			}
    			i += len;
    			ASM.EIP += len;
    		}
    		else
    		{
    			++i;
    			++ASM.EIP;
    		}
    	}
    
    	DWORD old_protect;
    
    	VirtualProtect(hook_address, 5, PAGE_EXECUTE_READWRITE, &old_protect);
    	InterlockedExchange(reinterpret_cast<ULONG*> (hook_address), *reinterpret_cast<ULONG*>(jump_code));
    	InterlockedExchange(reinterpret_cast<ULONG*> (reinterpret_cast<DWORD>(hook_address)+1), *reinterpret_cast<ULONG*>(jump_code+1));
    	 
    	VirtualProtect(hook_address, 5, old_protect, &old_protect);
    	data.orignal_funtcion_address = reinterpret_cast<PVOID>(reinterpret_cast<DWORD>(data.temp_function_address) + ASM_CODE_LEN);
    
    	if (argsCounts !=0)
    	{
    		byte ret_code[3];
    		ret_code[0] = 0xc2;
    		*reinterpret_cast<WORD*>(ret_code + 1) = argsCounts * 4;
    		mini_tool::mini_safe_write(reinterpret_cast<PVOID>(reinterpret_cast<DWORD>(data.temp_function_address) + ASM_NOT_PROCESS_OFFSET), ret_code, 3);
    	}
    
    	m_hookinfo.insert(std::map<const char *, hook_data>::value_type(flag, data));
    }
    
    void mini_hook::write_jmp(DWORD address, DWORD dest)
    {
    	DWORD code = dest - address - 5;
    	mini_tool::mini_safe_write(reinterpret_cast<PVOID>(address+1), &code, 4);
    }
    
    DWORD mini_hook::read_jmp(PVOID address)
    {
    	return READ_DWORD_WITH_OFFSET(address, 1) + reinterpret_cast<DWORD>(address) + 5;
    }
    
    void mini_hook::hook_detach(const char* flag)
    {
    	auto iter = m_hookinfo.begin();
    	while (iter != m_hookinfo.end())
    	{
    		if (_stricmp(flag, iter->first) == 0)
    		{
    			mini_tool::mini_safe_write(iter->second.hook_address, iter->second.bad_code, iter->second.bad_code_len);
    			VirtualFree(iter->second.temp_function_address, ASM_CODE_LEN, MEM_COMMIT);
    			m_hookinfo.erase(iter);
    			return;
    		}
    		++iter;
    	}
    }
    void mini_hook::detach_all()
    {
    	if (m_hookinfo.size() == 0)
    		return;
    
    	auto iter = m_hookinfo.begin();
    	while (iter != m_hookinfo.end())
    	{
    		mini_tool::mini_safe_write(iter->second.hook_address, iter->second.bad_code, iter->second.bad_code_len);
    		VirtualFree(iter->second.temp_function_address, ASM_CODE_LEN, MEM_COMMIT);
    		++iter;
    	}
    	m_hookinfo.clear();
    }



    mini_tool.cpp

    #include "stdafx.h"
    #include <windows.h>
    #include "mini_tool.h"
    void mini_tool::mini_safe_write(PVOID address, PVOID memory, DWORD size)
    {
    	DWORD old_protect;
    	if (IsBadReadPtr(address, size))
    	{
    		return;
    	}
    
    	VirtualProtect(address, size + 4, PAGE_EXECUTE_READWRITE,&old_protect);
    	memcpy(address,  memory, size);
    	VirtualProtect(address, size + 4, old_protect, &old_protect);
    }
    
    DWORD mini_tool::calc_jump_address(DWORD address)
    {
    	return address + READ_DWORD(address + 1) + 5;
    }
    
    void mini_tool::write_dwrod(PVOID address, DWORD value, DWORD offset /* = 0 */)
    {
    	*(DWORD*)((DWORD)address + offset) = value;
    }


     void				hook_on(PVOID hook_address, PVOID user_callback,const char * flag,int argsCounts=0);
    这个函数第一个参数就是需要hook的地址 第二个参数就是你的过滤函数,第三个参数就是hook_data的标志,第四个参数默认是0 也就是你要hook的函数的参数个数。

    第四个参数只有在你知道你要hook的函数原型(api hook)并且你是在函数的头部hook的时候才有用 其他时候慎用,因为看上面的汇编代码你就知道hook_context这个结构体里面

    有一个process_original_function的成员 默认值是1 就是表示继续执行原始函数,如果你不想执行原始的函数你就可以在你的hook处理函数中将context的这个成员设置为0,

    那么平栈的时候就会根据你的hook_on函数设置的参数个数来平栈,比如我hook的MsgBoxA 4个参数 那么就是retn 0x10,这样一般在函数的头部去hook 你知道参数的个数一般是没什么问题的,如果你是在某个函数的中间hook的 ,那么你必须要保证你设置process_original_function为0不继续执行原始函数的时候 直接retn args_coutns *4 的时候的堆栈是平衡的,如果你在某个函数的中间hook,然后在你的过滤函数里面设置为不继续执行原始函数,但是你没有设置参数个数 或者虽然你设置了参数个数但是retn 0x??之后的堆栈还是不平衡的,这样就是肯定不行的了。当然有兴趣的话可以给hook_data添加一个平栈函数的指针,然后写入你自定义分析出来的平栈代码,然后jmp 到你自己的平栈过程中去处理也是完全可以的。博主语文不好,可能说的乱七八糟,简而言之,你hook在某个api的头部那么你最好hook的时候设置清楚你hook的函数的参数个数,只有这样才能稳定的设置process_original_function这个成员,否则你不清楚函数的个数或者说懒得设置参数的个数 那么请你务必不要设置process_original_function这个成员!

    也就是说process_original_function这个功能完全是搭配你hook时候设置的args_counts的。如果你还不懂,那么你就直接调用hook_on(address,callback);

    void __stdcall callback(hook_context context)

    {

    // 在这里处理你要的东西

    }


    在你的过滤函数里面去监视你感兴趣的东西就好了 context里面的eax ebx等等寄存器你都可以读取和修改,你不懂的话那么你就不要动process_original_function这个成员即可。


    另外你的回调函数最好写成__stdcall 这样的函数是自己平栈的,cdecl貌似会有问题? 没深入了解了。。

    以上解释大牛直接无视就好了...看代码就懂了...  代码刚刚写的 ,,也许有一些不好的地方... 测试没什么问题.. 好了就这样吧..

    展开全文
  • hook 写一个Loading案例

    2019-02-14 12:06:12
    我们在实际的业务场景下,遇到一个需求: 对于一些加载比较慢的资源,组件最初展示标准的Loading效果,但在一定时间(比如2秒)后,变为“资源较大,正在积极加载,请稍候”这样的友好提示,资源加载完毕后再展示...

    原文地址
    我们在实际的业务场景下,遇到一个需求:

    对于一些加载比较慢的资源,组件最初展示标准的Loading效果,但在一定时间(比如2秒)后,变为“资源较大,正在积极加载,请稍候”这样的友好提示,资源加载完毕后再展示具体内容。

    对于一个展示的组件来说,我们希望的逻辑就是这样的:

    const PureDisplay = ({isLoading, isDelayed, data}) => {
        if (isDelayed) {
            return <div>'Please wait a little more...'</div>;
        }
    
        if (isLoading) {
            return <div>'Loading...'</div>;
        }
    
        return <div>{data}</div>;
    };
    

    通过isDelayed和isLoading这2个属性来表达3种状态(初始加载中、加载用时过长、已经加载完毕),使用条件分支展示不同的内容。

    在以往,我们很容易判断出来isDelayed的获取可以通过HOC来实现,因此我们也判断它可以用Hook来实现复用。但是在面对如何实现这个Hook的时候,出现了不小的疑惑,甚至无从下手。

    最后在一翻讨论后,我们发现一种方法,即先实现一个HOC版本,再“翻译”成对应的Hook,能快速完成对应的代码。

    HOC版

    假设我们需要实现一个withDelayHint的HOC来实现这一逻辑,简单整理了一下它的功能:

    • 知道组件当前是否在loading状态,如果不在的话就不用开定时器了。
    • 如果处在loading状态,则打开一个定时器,指定时间后将isDelayed由false改为true。
    • 如果loading状态发生了变化,则需要停掉定时器,并回到第1步重新判断是不是要开新的定时,用于组件状态更新的场合。

    基于上面的整理,HOC需要至少2个参数:

    • 如何获取loading状态。最简单地方法是提供一个属性的名称,直接从props[loadingPropName]拿,函数化一些可以提供一个函数来通过getLoading(props)获取。
    • 定时器的延迟时长,以毫秒为单位。
      因此,它的实现还是相对简单的:
    import React from 'react'
    const pureDisplay=({isLoading,isDelady,data})=>{
        if(isDelady){
            return <div>'Please wait a little more ...'</div>
        }
        if(isLoading){
            return <div>'Loading'</div>
        }
        return <div>数据:{data}</div>
    }
    const withDelayHint=(loadingName,delay)=>ComponentIn=>{
        const ComponentOut=class extends React.Component{
            state={
                timer:null,
                isDelady:false
            }
            tryStartTimer=()=>{
                this.setState({isDelady:false})
                //处于loading状态时 则开启定时器
                if(this.props[loadingName]){    
                    const timer=setTimeout(()=>{
                        this.setState({isDelady:true})
                    },delay)
                    this.setState({timer})
                }
            }
            componentDidMount(){
                this.tryStartTimer()
            }
            componentDidUpdate(prevProps){
                //如果数据回来后 则取消定时器
                if(prevProps[loadingName]!==this.props[loadingName]){
                    clearTimeout(this.state.timer);
                    this.tryStartTimer()                
                }
            }
            componentWillUnmount(){
                clearTimeout(this.state.timer);
            }
            render(){
                const {isDelady}=this.state;
                console.log('props',this.props);
                return <ComponentIn {...this.props} isDelady={isDelady}></ComponentIn>
            }
        }
        return ComponentOut;
    }
    //在使用上,将组件用HOC包装一次,即可以拿到isDelayed属性:
    const DisplayWithDelay=withDelayHint('isLoading',2000)(pureDisplay);
    export default DisplayWithDelay
    
    //使用
    <DisplayWithDelay isLoading={true} />
    

    Hook版

    在React官方提供的hook中,与组件中各种逻辑都有对应的版本,比如:

    • setState对应useState。
    • 生命周期对应useEffect。
      因此,我们把上面的代码一一通过映射来实现。需要注意的是,因为hook本身并不是组件的实现,所以是获取不到props的,因此hook不会有“从props中获取isLoading”这个逻辑,而是直接接收isLoading的值就行:
    import React, { useRef, useEffect, useState } from 'react'
    const PureDisplay = ({ isLoading, isDelady, data }) => {
        if (isDelady) {
            return <div>'Please wait a little more ...'</div>
        }
        if (isLoading) {
            return <div>'Loading'</div>
        }
        return <div>数据:{data}</div>
    }
    const useDelayHint = (loading, delay) => {
        //和 render无关的属性可以用 useRef 保存
        const timer = useRef(null);
        // setState ==> useState
        const [delayed, setDelayed] = useState(false);
        //生命周期
        useEffect(
            () => {
               const timer=loading?setTimeout(()=>setDelayed(true),delay):setDelayed(false);
                //清除逻辑
                return ()=>clearTimeout(timer);
            },
            //componentDidUpdate
            [loading]
        );
        return delayed;
    }
    

    使用

    const HookDisplay = props => {
        // 这里直接给isLoading,而不是loadingPropName
        const isDelayed = useDelayHint(props.isLoading, 2000);
    
        return <PureDisplay {...props} isDelayed={isDelayed} />;
    };
    

    可以看到,原本用于HOC的PureDisplay组件在此处还能继续用,这让HOC迁移到Hook的成本非常的小。

    展开全文
  • Android_Inline_Hook https://github.com/GToad/Android_Inline_Hook_ARM64 有32和64的实现,但是是分离的,要用的话还要自己把两份代码合并在一起。 缺点: 1、不支持函数替换(即hook后不执行原函数),现在只能...

    Android_Inline_Hook

    https://github.com/GToad/Android_Inline_Hook_ARM64
    有32和64的实现,但是是分离的,要用的话还要自己把两份代码合并在一起。

    缺点:
    1、不支持函数替换(即hook后不执行原函数),现在只能修改参数寄存器,无法修改返回值。
    2、不支持定义同类型的hook函数来接受处理参数,只能通过修改寄存器的方式修改参数。
    多余4个/或者占两个字节的参数,那么参数还要自己从栈上捞取。所以issues中说的把mov r0,sp去掉用来接收参数也是有问题的,就是参数在栈上的情况,传过来的时候sp不是原来的sp了。

    因为以上的两个缺点,想来没太多人用也是情理之中了,因为自己解析参数、不能修改返回值、不能不执行原函数,局限太大了。看来要想用起来,还得自己修改代码。。。

    whale

    https://github.com/asLody/whale
    移植好像比较简单,记得好像移植过,但是hook了某个系统函数回调原函数就崩溃了。所以用之前可能要先帮他找一遍bug、修复。lody的代码bug一直很多。。。

    后记:art hook的之前看过,应该说是xposed/frida的另一份代码,frida也是用的xposed的方式,只不过不修改系统文件、通过动态调用系统函数实现。frida是js的代码,这个是c/c++的实现。

    内联hook大概看了下其实也是一样的套路,32位采用ldr pc的方式跳到hook函数,64位使用x17寄存器跳到hook函数。剩下的修复原函数也是一样的。只是没时间完整看一遍定位bug了。

    HookZz

    https://github.com/jmpews/HookZz
    对于安卓程序员来说不太友好,编译需要cmake。
    windows:
    mkdir build_for_android_arm64 && cd build_for_android_arm64

    set ANDROID_NDK=D:\android\NDK\android-ndk-r16b

    C:\Users\EDZ\AppData\Local\Android\Sdk\cmake\3.10.2.4988404\bin\cmake … -DCMAKE_TOOLCHAIN_FILE=%ANDROID_NDK%/build/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI=“arm64-v8a” -DANDROID_NATIVE_API_LEVEL=android-21 -DSHARED=OFF -DHOOKZZ_DEBUG=OFF -G “Unix Makefiles” -D"CMAKE_MAKE_PROGRAM:PATH=D:\app\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin\mingw32-make.exe"

    需要指定-G “Unix Makefiles”,默认的NMake Makefiles编译不过,指定make,因为未设置环境变量,-D"CMAKE_MAKE_PROGRAM:PATH="

    编译动态库,-DSHARED=ON;编译静态库,-DSHARED=OFF。

    接下来为了方便还是移植到Android工程中吧。

    呃。。。基本不可用!Android:arm/arm64均crach,arm64可以修复,https://www.gitmemory.com/foundkey,在OneLib\stdcxx\LiteIterator.cc中函数initWithCollection添加inCollection->initIterator(innerIterator);

    但是arm还是crash,

    #00  pc 00025ef0  [anon:libc_malloc]
    12-12 15:12:53.685 181-181/? I/DEBUG:     #01  pc 000081f1  /data/app-lib/com.zhuotong.myhkzz-1/libhookzz.so (LiteCollectionIterator::getNextObject()+28)
    12-12 15:12:53.685 181-181/? I/DEBUG:     #02  pc 00007531  /data/app-lib/com.zhuotong.myhkzz-1/libhookzz.so (gen_thumb_relocate_code(void*, int*, unsigned int, unsigned int)+312)
    12-12 15:12:53.685 181-181/? I/DEBUG:     #03  pc 00007ac1  /data/app-lib/com.zhuotong.myhkzz-1/libhookzz.so (InterceptRouting::Prepare()+56)
    12-12 15:12:53.685 181-181/? I/DEBUG:     #04  pc 00007cc1  /data/app-lib/com.zhuotong.myhkzz-1/libhookzz.so (FunctionInlineReplaceRouting::Dispatch()+12)
    12-12 15:12:53.685 181-181/? I/DEBUG:     #05  pc 00007d4d  /data/app-lib/com.zhuotong.myhkzz-1/libhookzz.so (ZzReplace+120)
    

    且通过arm64测试来看,open、fopen可以hook成功。__system_property_get函数,第二个参数既是入参也做返回参数的情况无法正确hook,可以hook到,但是回调原函数,无论是使用第二个参数还是new参数均无法得到值,所以肯定哪里存在bug。

    一个函数只能被hook一次,再次hook调用原函数(备份的第一个hook函数)崩溃,所以两次hook不能再调用原函数。

    基于以上种种情况,可能还是自己实现Android_Inline_Hook比较好,毕竟Android_Inline_Hook代码易懂,hookZz太多无关代码,没时间看架构了。

    后记:后来大概看了一下,32位也是ldr pc实现,好像也做了保存寄存器等操作,和Android_Inline_Hook基本是一样的,64位好像也是使用x17寄存器。其他也都是大同小异。所以也是没时间完整看一遍定位bug了。

    自实现inline hook

    因为以上的问题,目前/或者之前用过的一些hook框架或多或少都有些较大的bug(hookzz之前的某个版本应该是可以的),而对其进行修复成本较高,还不如自己写一个。

    首先统一一些概念。把覆盖被hook函数的指令称为跳板0,因为这部分指令越短越好,所以其大多数情况下只是一个跳板,跳到真正的用于hook指令处,这部分指令称为shellcode(shellcode不是必须的,比如跳板0直接跳到hook函数去执行,那么就不需要shellcode)。

    这里假设都有一些arm指令的基础了,或者对hook已经有些理解了。后来我想了下我这篇更偏向于怎么写一个稳定可用hook框架,更偏向设计、编程,所以适合已经有些基础的,不是从0讲述hook到实现,虽然接下来的部分也会有很细节的部分,但是是针对一些特定的点。建议可以先看下其他人,比如ele7enxxh、GToad写的一些文章。

    inline hook这种东西,我是感觉当你掌握汇编、自己有需求的情况下,不经过学习也是可以从0写出一个hook框架的,确实是原理很简单的。

    最简单的实现

    最容易想到的一种实现方式,使用跳板0覆盖一个函数的指令,当执行到这个函数的时候其实就是执行跳板0,跳板0在不修改寄存器的情况跳到执行hook函数。如果在hook函数内不需要执行原函数是最简单的,到这hook就是一个完整的hook。

    如果需要执行原函数,那么在跳板0覆盖指令之前先备份指令,执行原函数之前把备份的指令再覆盖回去,执行之后再覆盖回跳板0。确实是最简单的方法、也确实可以,但是也有一个很大的问题,就是多线程的问题,在把备份的指令覆盖回去之后,其他线程执行到这里不就hook不到了,甚至crash。

    因为无法加锁(真正有效的锁),而暂停所有线程的太重了,所以基本上只有自己确定某个函数不存在多线程问题或者无需调用原函数才可用。写个demo,熟悉下hook还行,真的实际使用是不行的。

    也是因为这个问题,基本上目前的inline hook都会避免再次覆盖指令。不能覆盖回去,那么就直接执行备份的指令,执行后再跳到跳板0之后再继续执行。

    也确实是可行的,大部分指令是可以这么做的,但是也有例外。比如备份的指令中有b/bl指令跳到一个偏移地址,跳转到的地址等于当前地址+偏移。而备份后的指令取得当前地址肯定是不等于原来的当前地址的,所以就跳到错误的地址去执行了。

    好在我们可以进行修复,计算出原来要跳到的绝对地址,把这条b指令替换成ldr pc指令。其他指令的一些修复也是类似的道理。

    四种hook形式应用不同的场景

    dump读写寄存器、堆栈–一种hook形式

    Android_Inline_Hook就是这样的实现,只能接收读写寄存器和堆栈,原函数还运行。那么其实这种方式主要作用就是读写参数寄存器、栈,不能读写函数返回值,如果是不受参数控制流程的函数就无能为力了(当然是可以逆向分析,在已经得到返回值的指令处hook,但是应用场景太小了)。

    那么实现的核心就是,跳板0->dumpshellcode。dumpshellcode把寄存器以数组的形式存放(栈就是天然的数组),把这个数组传递给dump函数,dump函数接收处理寄存器(未生效)。dump函数返回shellcode,恢复数组数据到寄存器,完成恢复或者修改。执行backupshellcode(备份的原函数的开头部分,修复pc,跳回原函数之后的部分)完成原函数的执行流程。

    替换/拦截函数,接收处理参数,调用原函数、读写返回值–一种hook形式

    最常用、最符合使用习惯的的方式。
    实现的核心,跳板0->funshellcode。funshellcode可以在上面的dumpshellcode基础上实现也可以全新实现。

    在dumpshellcode基础上可以取巧一些,前面的保存dump寄存器保留,把后面的执行backupshellcode换成执行hook函数。

    全新实现就是不保存寄存器,那么就可以把hook函数放在跳板shellcode中,直接跳到hook函数;也可以跳板0->funshellcode,funshellcode中再跳转到hook函数。

    之后就进入hook函数,如果不调用原函数,直接返回一个返回值或者void函数什么都不做即可(如果是参数也做返回值的情况就修改参数)。如果调用原函数,就通过一个结构体/map等查询被hook函数地址对应的backupshellcode,把backupshellcode转成函数指针,传参调用,即可完成原函数的调用、或者返回值。

    dump读写寄存器、堆栈,调用原函数、读写返回值/寄存器–一种hook形式

    在dump的基础上,执行backupshellcode(备份的原函数的开头部分,修复pc,跳回原函数之后的部分)完成原函数的执行流程之后返回到dumpshellcode,调用另一个dump函数,读写返回值(r0/x0,r1/x1寄存器)。不同于dump的地方在于如果要返回到dumpshellcode,那么在调用backupshellcode之前应该备份原来的lr寄存器。考虑到多线程的问题,肯定是不能用一个固定的变量/地址去存储lr寄存器的,可能被覆盖,同一个线程哪怕是递归调用函数也是有顺序的,所以每个线程的被hook函数使用一个顺序的容器保存lr,后进先出。

    dump读写寄存器、堆栈,读写返回值/寄存器–一种hook形式

    在dump的基础上,不执行backupshellcode(备份的原函数的开头部分,修复pc,跳回原函数之后的部分),直接修改r0/x0、r1/x1寄存器,返回。和dump函数很多地方是一致的,应用于只需要返回值,并不需要原函数执行的情况。

    以上四种场景应该是足够满足hook的需要了。

    arm64 实现难点

    因为无法直接操作pc,那么实现跳转(通用情况)需要占用一个寄存器。要么使用一个不会被使用的寄存器(哪有绝对不会被使用的寄存器),要么先保存这个寄存器,通过栈保存(之前就是忽略了这个问题在固定地址保存寄存器,那么多线程情况下就可能被覆盖),跳过去之后先恢复这个寄存器。例如:

    stp     X1, X0, [SP, #-0x10];//保存寄存器
    ...
    ldr     x0, [sp, #-0x8];//恢复x0寄存器
    

    对应shellcode我们很容易实现,但是如果是c/c++函数(我们的hook函数)就麻烦了,直接在函数开头插代码肯定是不行的。在源码中函数第一行内嵌汇编恢复寄存器?很可惜,这种只在无参、无返回值(空实现)、只有内嵌汇编的情况会成功,其他情况下源码中的第一行并不是汇编中的第一行。。。

    所以似乎陷入了无解的状态,在llvm中函数开头插指令?似乎太重了。所以回到原点了,就要考虑真的没有不使用寄存器跳转的可能吗?其实还是有的,b或者bl到偏移

    ARM64:

    B:0x17向前跳转,0x14向后跳转

    BL:0x97向前跳转 0x94向后跳转

    偏移地址计算过程:

    (目标地址 - 指令地址)/ 4 = 偏移

    // 减8,指令流水造成。

    // 除4,因为指令定长,存储指令个数差,而不是地址差。

    完整指令:

    .text:000000000008CC84 8D B3 FF 97 BL je_arena_malloc_hard

    .text:0000000000079AB8 je_arena_malloc_hard

    计算偏移:

    (79AB8-8CC84) / 4 = FFFFFFFFFFFFB38D

    FFB38D | 0x97000000 = 97FFB38D

    所以理论上,如果被hook的函数和hook函数/跳板/shellcode的距离在正负67108860/64m的范围内,64m=67108864,还有0,比如BL 0=00000094。那么这样就可以省一个寄存器了,针对arm64不能直接操作pc的问题,这应该是一个解决方案。

    那么单指令hook除了异常的方式是不是就是指的这种方式呢?只需要覆盖一条指令,关键是怎么确保被hook函数和hook函数地址在正负64m内呢。怎么能申请到这块地址的内存呢。

    也许可以查看proc/pid/maps,在要hook的so附近寻找未使用的空间,然后使用mmap申请,不确定是否可行。

    暂时可用的一些方式如下(最终未采用):

    自定义section,增加蹦床,可读写执行

    实际上到内存中还是被和代码段放在一起都是可读可执行,没有写的权限。

    .section ".my_sec", "awx"
    .global _myopen_start
    
    //.text
    
    _myopen_start:
        ldr     x0, [sp, #-0x8];
        b       my_open;
    
    .end
    
    //用于手动生成蹦床代码,因为hook代码和这个蹦床一起编译的,所以基本上不会超出加减64m,那么就可以使用b跳转
    //到偏移,就不用占用一个寄存器了。需要每增加一个hook函数,就加一个蹦床,相应的生成shellcode中跳转到hook
    //函数的地方都要改成这个蹦床的地址。难就难在这个不好通过内嵌汇编实现,因为b跟的是个偏移值,在汇编中可以使用
    //函数名称,编译器替换,但是内嵌汇编中不行。而自己计算
    

    shellcode如下,

    //用于演示,因为不是最终方案,不写完整代码了。
    
    //修改trampoline指向蹦床即可
    
    .data
    
    replace_start:
    
        ldr     x0, trampoline;             //如果每一个函数都在源码中新建一个shellcode的话,而不是动态复制生成,那么这个shellcode和蹦床可以合为一个。
        br      x0;                         //不能改变lr寄存器
    
    
    trampoline:                           //蹦床的地址
    .double 0xffffffffffffffff
    
    replace_end:
    

    awx指定读写执行,在elf中(ida查看)确实是读写执行。如果这个section中仅包含变量,那么在内存中放在可读写的段;如果存在代码或者代码和变量均存在,都是和代码段一样仅可读执行。那么在单独的section中存放蹦床代码意义也不大了,还是需要调用mprotect修改内存权限。不过考虑到如果放在仅可读写的段中,那么万一映射后的rw-p和r-xp超过了64m,不就白瞎了,所以还是以代码或者至少这个section中存在一个函数,保证和被hook的函数都在r-xp。

    77c5545000-77c557c000 r-xp 00000000 103:37 533353                        /data/app/com.zhuotong.myinhk-TRvqt0ReHRjQeeAnpXDnFQ==/lib/arm64/libInlineHook.so
    77c558b000-77c558e000 r--p 00036000 103:37 533353                        /data/app/com.zhuotong.myinhk-TRvqt0ReHRjQeeAnpXDnFQ==/lib/arm64/libInlineHook.so
    77c558e000-77c558f000 rw-p 00039000 103:37 533353                        /data/app/com.zhuotong.myinhk-TRvqt0ReHRjQeeAnpXDnFQ==/lib/arm64/libInlineHook.so
    

    而如果直接和.text一起,主要是怕对这块内存修改权限引发什么异常,不确定如果代码正在执行,修改权限是否会出问题,所以最好能仅修改蹦床的区间。而且不确定是否有只能修改为读写和读执行的系统,所以可能要先修改成读写,写了之后再修改成读执行?这样会不会也有几率触发问题,但是如果每个蹦床都分配一个页的内存也不现实。。。

    内嵌汇编,在自定义section中增加蹦床

    在这里插入图片描述

    这样定义一个无参无返回的函数,倒是可以使这个函数就是内嵌汇编,但是不确定如果ollvm混淆等是否会被拆分、加入垃圾代码等。

    而且问题在于怎么自动生成一个蹦床函数。宏定义?那这个宏要在函数之外,不太容易自动化实现,包括蹦床的函数名称、b后面的函数名称。和上面的汇编中添加一个蹦床一样的问题,除非自己实现预处理之类的自动插入汇编代码、宏等,似乎不太现实。。。

    似乎很难实现自动化,手动写代码太麻烦且和arm的部分接口、行为不一致。但是如果自己简单测试、不在乎这些也是可以的。

    在自定义section中预留蹦床空间

    unsigned int hkArry[256*2] __attribute__ ((section(".my_sec")));
    
    //下面为伪代码,通过这样也可以实现不修改/保存寄存器、不修改hook函数,动态生成蹦床完成hook。
    
    //优点是不用保存寄存器,缺点是因为正负64m的限制,hook函数应该和hook库是一起编译成动态库/可执行文件的。不能单独使用hook库。
    
    //256个蹦床,实际使用可能要考虑这个hkArry内存对齐的问题(如果编译器未做内存对齐)。
    //unsigned int hkArry[256 * 2] __attribute__ ((section(".my_sec")));
    
    //void test_trampoline(){
        //仅用于演示,应该先设置内存为可读写/执行,
    //    hkArry[0] = 0xf85f83e0;//或者memcpy, ldr     x0, [sp, #-0x8];
        //例如这样计算偏移,组合b指令。
    //    unsigned long offset = (unsigned long) my_open - ((unsigned long) hkArry + 1 * 4);
    //    hkArry[1] = 0x14000000;//计算偏移,生成指令,b       0;
    //}
    

    256个蹦床。动态生成蹦床代码,第一条指令固定,第二条指令计算生成。需要hkArry修改为读写执行。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H3VXRUg4-1577937079577)(https://i.imgur.com/1dNxFJm.png)]

    未实现

    so的r-xp中应该是有未使用的多余的内存的,为了对齐、页面等,所以怎么确定多余的空间大小和位置,然后蹦床代码存放其中。

    为什么尽量不使用x16、x17等不用保存的寄存器?

    因为种种限制,所以最后采用的还是保存一个寄存器,而使用哪个寄存器呢,我是选的lr寄存器。为什么不用x16、x17等不用保存的寄存器?这里就涉及到一个标准和规范的问题。

    理论上编译器编译的c/c++函数是遵守这个规范的,要么不使用x16、x17寄存器,要么只是临时中转,不会在调用其他函数之后再从x16、x17寄存器取值(因为其他函数可能改变x16、x17),但是内嵌汇编(虽然指定不使用x16、x17寄存器,但还是被编译器使用了)或者人工写的汇编,虽然不常见,但确实存在。而最常见的是plt函数内都是使用x16、x17做中转。所以使用一个不一定会被保存的寄存器不如使用一个会被保存的寄存器。而因为lr寄存器的特殊性,一般使用的话都会先压栈保存,结束恢复(32位不一定恢复,64位是会恢复lr寄存器的,因为不能直接操作pc,多数都是恢复lr,再ret)

    所以其实主要还是兼容性考虑,尽量采用一些绕弯的方式不改变任何寄存器,实在没办法的情况下或者标准c/c++函数、非函数任意位置hook的情况下才使用x16、x17寄存器。

    解析寄存器、栈取出参数,调用hook函数

    难点在于:
    1、不知道参数个数、参数类型。
    2、需要确定各种类型参数占几个寄存器、可变参数等

    其实在源码中定义hook函数的时候是有函数原型的,但是运行时拿不到。忽然想到"c++"函数名的规则,里面包含参数、返回值类型,似乎可行,但是很多情况并不希望导出函数,而且也并不一定都是c++实现的。

    那么如果定义一个接口,传入函数原型也不是不行,基本类型就用相应的字符或者枚举标识,其他所有的都是void*指针。可还是怕碰见可变参数函数,不确定参数个数,参数类型,只有接收/实现函数才知道。所以似乎不太可行。

    而libffi似乎不太适合这个情况,也是需要明确指令参数和返回类型,还要传输参数。

    目前的实现没有经过自己解析参数,只是中转,通过定义一致的函数原型,让编译器帮助我们解析参数。

    代码实现_arm64_统一的跳板0

    因为arm64限制条件最多,那么应该先实现arm64,这样才能更好的保证api的通用/一致性,因为arm64不能操作pc不得不这么做,arm32同样也可以这么做,但是如果先实现arm32,可能就先入为主的设计一些arm32可用的api。这算是一种架构思维吧,或者多思考一下就明白了。

            STP X1, X0, [SP, #-0x10];//因为要使用一个寄存器,所以先保存,当然不一定是x0
            LDR X0, 8;    //把shellcode/hook函数地址存到x0
            BR X0;    //执行shellcode/hook函数
            ADDR(64)
            LDR X0, [SP, -0x8];//因为不能操作pc,所以跳回来的时候免不了也要使用一个寄存器
    

    其实很简单,注释基本都说明了,使用了24字节,所以如果能确定要hook的函数是标准的c/c++函数,不会使用x16、x17去保存值的话也可以这样,使用16字节,降低被hook函数太短失败的概率。

        LDR    X17, 8;
        BR    x17;
        ADDR(64)
    

    代码实现_arm64_dump函数

    .include "../../asm/base.s"
    
    //.extern _dump_start
    //.extern _dump_end
    //.extern _hk_info
    
    //.set _dump_start, r_dump_start
    //.set _dump_end, r__dump_end
    //.set _hk_info, r__hk_info
    
    //.global _dump_start
    //.global _dump_end
    //.global _hk_info
    
    //.hidden _dump_start
    //.hidden _dump_end
    //.hidden _hk_info
    
    //可用于标准的c/c++函数、非标准函数、函数的一部分(用于读写寄存器),前提都是字节长度足够
    //非标准函数即非c/c++编译的函数,那么手写汇编可能存在并不遵守约定的情况,比如我们使用了sp寄存器,并在未使用的栈上保存寄存器
    //但是可能不是满递减而是反过来满递增,或者不遵守栈平衡,往栈上写数据,但是并不改变sp寄存器。当然应该是很少见的。
    
    .data
    
    _dump_start:                    //用于读写寄存器/栈,需要自己解析参数,不能读写返回值,不能阻止原函数(被hook函数)的执行
                                    //从行为上来我觉得更偏向dump,所以起名为dump。
        sub     sp, sp, #0x20;      //跳板在栈上存储了x0、x1,但是未改变sp的值
    
        mrs     x0, NZCV
        str     x0, [sp, #0x10];    //覆盖跳板存储的x1,存储状态寄存器
        str     x30, [sp];          //存储x30
        add     x30, sp, #0x20
        str     x30, [sp, #0x8];    //存储真实的sp
        ldr     x0, [sp, #0x18];    //取出跳板存储的x0
    
    save_x0_x29://保存寄存器x0-x29
        sub     sp, sp, #0xf0;      //分配栈空间
        stp     X0, X1, [SP];       //存储x0-x29
        stp     X2, X3, [SP,#0x10]
        stp     X4, X5, [SP,#0x20]
        stp     X6, X7, [SP,#0x30]
        stp     X8, X9, [SP,#0x40]
        stp     X10, X11, [SP,#0x50]
        stp     X12, X13, [SP,#0x60]
        stp     X14, X15, [SP,#0x70]
        stp     X16, X17, [SP,#0x80]
        stp     X18, X19, [SP,#0x90]
        stp     X20, X21, [SP,#0xa0]
        stp     X22, X23, [SP,#0xb0]
        stp     X24, X25, [SP,#0xc0]
        stp     X26, X27, [SP,#0xd0]
        stp     X28, X29, [SP,#0xe0]
    
    call_onPreCallBack://调用onPreCallBack函数,第一个参数是sp,第二个参数是STR_HK_INFO结构体指针
        mov     x0, sp;                 //x0作为第一个参数,那么操作x0=sp,即操作栈读写保存的寄存器
        ldr     x1, _hk_info;
        ldr     x3, [x1];               //onPreCallBack
        bl      get_lr_pc;              //lr为下条指令
        add     lr, lr, #8;              //lr为blr x3的地址
        str     lr, [sp, #0x108];        //lr当作pc,覆盖栈上的x0
        blr     x3
    
    restore_regs://恢复寄存器
        ldr     x0, [sp, #0x100];       //取出状态寄存器
        msr     NZCV, x0
    
        ldp     X0, X1, [SP];           //恢复x0-x29寄存器
        ldp     X2, X3, [SP,#0x10]
        ldp     X4, X5, [SP,#0x20]
        ldp     X6, X7, [SP,#0x30]
        ldp     X8, X9, [SP,#0x40]
        ldp     X10, X11, [SP,#0x50]
        ldp     X12, X13, [SP,#0x60]
        ldp     X14, X15, [SP,#0x70]
        ldp     X16, X17, [SP,#0x80]
        ldp     X18, X19, [SP,#0x90]
        ldp     X20, X21, [SP,#0xa0]
        ldp     X22, X23, [SP,#0xb0]
        ldp     X24, X25, [SP,#0xc0]
        ldp     X26, X27, [SP,#0xd0]
        ldp     X28, X29, [SP,#0xe0]
        add     sp, sp, #0xf0
    
        ldr     x30, [sp];              //恢复x30
        add     sp, sp, #0x20;          //恢复为真实sp
    
    call_oriFun:
        stp     X1, X0, [SP, #-0x10];   //因为跳转还要占用一个寄存器,所以保存
        ldr     x0, _hk_info;
        ldr     x0, [x0, #8];           //pOriFuncAddr
        br      x0
    
    get_lr_pc:
        ret;                            //仅用于获取LR/PC
    
    _hk_info:                           //结构体STR_HK_INFO
    .double 0xffffffffffffffff
    
    _dump_end:
    
    .end
    
    
    

    基本上注释已经说明了。_hk_inf为如下结构体的指针,onPreCallBack函数原型如下。

    //hook信息
    typedef struct STR_HK_INFO{
        void (*onPreCallBack)(struct my_pt_regs *, struct STR_HK_INFO *pInfo);       //回调函数,在执行原函数之前获取参数/寄存器的函数
        void * pOriFuncAddr;             //存放备份/修复后原函数的地址
        void (*pre_callback)(struct my_pt_regs *, struct STR_HK_INFO *pInfo);      //pre_callback,内部做保存lr的操作,之后回调onPreCallBack,不能被用户操作
        void (*onCallBack)(struct my_pt_regs *, struct STR_HK_INFO *pInfo);            //回调函数,执行原函数之后获取返回值/寄存器的函数
        void (*aft_callback)(struct my_pt_regs *, struct STR_HK_INFO *pInfo);       //aft_callback,内部做恢复lr的操作,之后回调onCallBack,不能被用户操作
        void *pHkFunAddr;                 //hook函数,即自定义按照被hook的函数原型构造,处理参数/返回值的函数
        //以上为在shellcode中通过偏移直接或间接用到的,所以如果有变动,相应的shellcode也要跟着变动
    
        void **ppHkFunAddr;                     //上面pHkFunAddr函数在shellcode中的地址,已废弃;
        void **hk_infoAddr;                     //shellcode中HK_INFO的地址
        void *pBeHookAddr;                      //被hook的地址/函数
        void *pStubShellCodeAddr;               //跳过去的shellcode stub的地址
        size_t shellCodeLength;                 //上面pStubShellCodeAddr的字节数
        void ** ppOriFuncAddr;                  //shellcode 中存放备份/修复后原函数的地址,已废弃,待去除;改成上面pHkFunAddr函数的指针,应用于解除hook
        void *pNewEntryForOriFuncAddr;          //和pOriFuncAddr一致
        BYTE szbyBackupOpcodes[OPCODEMAXLEN];   //原来的opcodes
        int backUpLength;                       //备份代码的长度,arm64模式下为24
        int backUpFixLengthList[BACKUP_CODE_NUM_MAX]; //保存
        const char* methodName;
    } HK_INFO;
    
    
    #if defined(__aarch64__)
    struct my_pt_regs {
        __u64 uregs[31];
        __u64 sp;
        __u64 pstate;       //有时间应该修复,pc在前,但是涉及到栈和生成shellcode都要改,先这么用吧,和系统结构体有这点不同
        __u64 pc;
    };
    
    #define ARM_lr uregs[30]
    #define ARM_sp sp
    //#define SP (__u64*)sp
    //#define SP_32(i) *(__u32*)((__u64)(regs->sp) + i*4)
    //#define SP_32(i) *((__u32*)regs->sp+i)
    //#define SP_32(i) *((__u64*)regs->sp+i)
    #define SP(i) *((__u64*)regs->sp+i)
    
    
    #elif defined(__arm__)
    
    struct my_pt_regs {
      long uregs[18];
    };
    
    #ifndef __ASSEMBLY__
    
    #define ARM_cpsr uregs[16]
    #define ARM_pc uregs[15]
    #define ARM_lr uregs[14]
    #define ARM_sp uregs[13]
    #define ARM_ip uregs[12]
    #define ARM_fp uregs[11]
    #define ARM_r10 uregs[10]
    #define ARM_r9 uregs[9]
    #define ARM_r8 uregs[8]
    #define ARM_r7 uregs[7]
    #define ARM_r6 uregs[6]
    #define ARM_r5 uregs[5]
    #define ARM_r4 uregs[4]
    #define ARM_r3 uregs[3]
    #define ARM_r2 uregs[2]
    #define ARM_r1 uregs[1]
    #define ARM_r0 uregs[0]
    #define ARM_ORIG_r0 uregs[17]
    #define ARM_VFPREGS_SIZE (32 * 8 + 4)
    
    #endif
    
    #define SP(i) *((__u32*)regs->ARM_sp+i)
    
    #endif
    
    

    my_pt_regs对应在栈上存放的寄存器。这里面其他寄存器都容易保存,pc寄存器因为不能直接操作,所以要取巧一些。我是利用bl跳转之前会把下一条指令的地址存放到lr寄存器,那么再跳回读取lr寄存器即可。

        bl      get_lr_pc;              //lr为下条指令
        add     lr, lr, #8;              //lr为blr x3的地址
        str     lr, [sp, #0x108];        //lr当作pc,覆盖栈上的x0
        blr     x3
        ...
    get_lr_pc:
        ret;                            //仅用于获取LR/PC
    

    当然这里保存pc寄存器其实也不是必须的。

    dump_demo

    /**
     *  用户自定义的stub函数,嵌入在hook点中,可直接操作寄存器等
     * @param regs      寄存器结构,保存寄存器当前hook点的寄存器信息
     * @param pInfo     保存了被hook函数、hook函数等的结构体
     */
    void onPreCallBack(my_pt_regs *regs, HK_INFO *pInfo) //参数regs就是指向栈上的一个数据结构,由第二部分的mov r0, sp所传递。
    {
        const char* name = "null";
        if (pInfo) {
            if (pInfo->methodName) {
                name = pInfo->methodName;
            } else {
                char buf[20];
                sprintf(buf, "%p", pInfo->pBeHookAddr);
                name = buf;
            }
        }
    //    LE("tid=%d onPreCallBack:%s", gettid(), name);
    
    #if defined(__aarch64__)
    
        LE("tid=%d, onPreCallBack:%s, "
           "x0=0x%llx, x1=0x%llx, x2=0x%llx, x3=0x%llx, x4=0x%llx, x5=0x%llx, x6=0x%llx, x7=0x%llx, x8=0x%llx, x9=0x%llx, x10=0x%llx,"
           " x11=0x%llx, x12=0x%llx, x13=0x%llx, x14=0x%llx, x15=0x%llx, x16=0x%llx, x17=0x%llx, x18=0x%llx, x19=0x%llx, x20=0x%llx, "
           "x21=0x%llx, x22=0x%llx, x23=0x%llx, x24=0x%llx, x25=0x%llx, x26=0x%llx, x27=0x%llx, x28=0x%llx, x29/FP=0x%llx, x30/LR=0x%llx, "
           "cur_sp=%p, ori_sp=%p, ori_sp/31=0x%llx, NZCV/32=0x%llx, x0/pc/33=0x%llx, cur_pc=%llx, arg8=%x, arg9=%x, arg10=%x, arg11=%x, "
           "arg12=%x, arg13=%x;"
           , gettid(), name,
           regs->uregs[0], regs->uregs[1], regs->uregs[2], regs->uregs[3], regs->uregs[4], regs->uregs[5],
           regs->uregs[6], regs->uregs[7], regs->uregs[8], regs->uregs[9], regs->uregs[10], regs->uregs[11],
           regs->uregs[12], regs->uregs[13], regs->uregs[14], regs->uregs[15], regs->uregs[16], regs->uregs[17],
           regs->uregs[18], regs->uregs[19], regs->uregs[20], regs->uregs[21], regs->uregs[22], regs->uregs[23],
           regs->uregs[24], regs->uregs[25], regs->uregs[26], regs->uregs[27], regs->uregs[28], regs->uregs[29], regs->uregs[30],
           regs, ((char*)regs + 0x110), regs->uregs[31], regs->uregs[32], regs->uregs[33], regs->pc,
           SP(0), SP(1), SP(2), SP(3), SP(4), SP(5)
        );
    
    #elif defined(__arm__)
        LE("tid=%d, onPreCallBack:%s, "
           "r0=0x%lx, r1=0x%lx, r2=0x%lx, r3=0x%lx, r4=0x%lx, r5=0x%lx, r6=0x%lx, r7=0x%lx, r8=0x%lx, r9=0x%lx, r10=0x%lx, r11=0x%lx, r12=0x%lx, "
           "cur_sp=%p, ori_sp=%p, ori_sp/13=0x%lx, lr=0x%lx, cur_pc=0x%lx, cpsr=0x%lx, "
           "arg4=0x%lx, arg5=0x%lx, arg4=0x%lx, arg5=0x%lx;"
        , gettid(), name,
           regs->uregs[0], regs->uregs[1], regs->uregs[2], regs->uregs[3], regs->uregs[4], regs->uregs[5],
           regs->uregs[6], regs->uregs[7], regs->uregs[8], regs->uregs[9], regs->uregs[10], regs->uregs[11],
           regs->uregs[12],
           regs, ((char*)regs + 0x44), regs->uregs[13], regs->uregs[14], regs->uregs[15], regs->uregs[16],
           regs->uregs[17], regs->uregs[18], SP(0), SP(1)
        );
    #endif
    
        if (pInfo) {
            LE("onPreCallBack: HK_INFO=%p", pInfo);
            if (pInfo->pBeHookAddr == open && regs->uregs[0]) {
                const char* name = (const char *)(regs->uregs[0]);
                LE("onPreCallBack: open: %s , %o, %o", name, regs->uregs[1], (mode_t)regs->uregs[2]);
            }
        }
    
    }
    
    
    void test_dump(){
        LE("open=%p, callback=%p", open, onPreCallBack);
        if (dump((void *)(open), onPreCallBack, NULL, "open") != success) {
            LE("hook open error");
        }
    
        int fd = open("/system/lib/libc.so", O_RDONLY);
        LE("open /system/lib/libc.so, fd=%d", fd);
    }
    

    自定义onPreCallBack修改寄存器即可。如果是一个函数,那么能控制的是参数,不能阻止原函数的调用。

    代码实现_arm64_dump_with_ret函数

    //.include "../../asm/base.s"
    
    .global r_dump_start
    .global r_dump_end
    .global r_hk_info
    
    .hidden r_dump_start
    .hidden r_dump_end
    .hidden r_hk_info
    
    
    .data
    
    r_dump_start:                    //用于读写寄存器/栈,需要自己解析参数,不能读写返回值,不能阻止原函数(被hook函数)的执行
                                    //从行为上来我觉得更偏向dump,所以起名为dump。
        sub     sp, sp, #0x20;      //跳板在栈上存储了x0、x1,但是未改变sp的值
    
        mrs     x0, NZCV
        str     x0, [sp, #0x10];    //覆盖跳板存储的x1,存储状态寄存器
        str     x30, [sp];          //存储x30
        add     x30, sp, #0x20
        str     x30, [sp, #0x8];    //存储真实的sp
        ldr     x0, [sp, #0x18];    //取出跳板存储的x0
    
    save_x0_x29://保存寄存器x0-x29
        sub     sp, sp, #0xf0;      //分配栈空间
        stp     X0, X1, [SP];       //存储x0-x29
        stp     X2, X3, [SP,#0x10]
        stp     X4, X5, [SP,#0x20]
        stp     X6, X7, [SP,#0x30]
        stp     X8, X9, [SP,#0x40]
        stp     X10, X11, [SP,#0x50]
        stp     X12, X13, [SP,#0x60]
        stp     X14, X15, [SP,#0x70]
        stp     X16, X17, [SP,#0x80]
        stp     X18, X19, [SP,#0x90]
        stp     X20, X21, [SP,#0xa0]
        stp     X22, X23, [SP,#0xb0]
        stp     X24, X25, [SP,#0xc0]
        stp     X26, X27, [SP,#0xd0]
        stp     X28, X29, [SP,#0xe0]
    
    call_onPreCallBack://调用onPreCallBack函数,第一个参数是sp,第二个参数是STR_HK_INFO结构体指针
        mov     x0, sp;                 //x0作为第一个参数,那么操作x0=sp,即操作栈读写保存的寄存器
        ldr     x1, r_hk_info;
        ldr     x3, [x1, #0x10];        //pre_callback
        bl      get_lr_pc;              //lr为下条指令
        add     lr, lr, 8;              //lr为blr x3的地址
        str     lr, [sp, #0x108];        //lr当作pc,覆盖栈上的x0
        blr     x3
    
    to_call_oriFun:
        ldr     x0, [sp, #0x100];       //取出状态寄存器
        msr     NZCV, x0
    
        ldp     X0, X1, [SP];           //恢复x0-x29寄存器
        ldp     X2, X3, [SP,#0x10]
        ldp     X4, X5, [SP,#0x20]
        ldp     X6, X7, [SP,#0x30]
        ldp     X8, X9, [SP,#0x40]
        ldp     X10, X11, [SP,#0x50]
        ldp     X12, X13, [SP,#0x60]
        ldp     X14, X15, [SP,#0x70]
        ldp     X16, X17, [SP,#0x80]
        ldp     X18, X19, [SP,#0x90]
        ldp     X20, X21, [SP,#0xa0]
        ldp     X22, X23, [SP,#0xb0]
        ldp     X24, X25, [SP,#0xc0]
        ldp     X26, X27, [SP,#0xd0]
        ldp     X28, X29, [SP,#0xe0]
        add     sp, sp, #0xf0
    
        ldr     x30, [sp];              //恢复x30
        add     sp, sp, #0x20;          //恢复为真实sp
    
        stp     X1, X0, [SP, #-0x10];   //因为跳转还要占用一个寄存器,所以保存
        ldr     x0, r_hk_info;
        ldr     x0, [x0, #8];           //pOriFuncAddr
        blr     x0;
    
    to_aft_callback:                //有时间再把这部分代码优化掉,是可以再跳转到开头的,因为这部分代码都是一样的,判断lr可以区分出来的
        STP     X1, X0, [SP, #-0x10]
        sub     sp, sp, #0x20;      //跳板在栈上存储了x0、x1,但是未改变sp的值
    
        mrs     x0, NZCV
        str     x0, [sp, #0x10];    //覆盖跳板存储的x1,存储状态寄存器
        str     x30, [sp];          //存储x30
        add     x30, sp, #0x20
        str     x30, [sp, #0x8];    //存储真实的sp
        ldr     x0, [sp, #0x18];    //取出跳板存储的x0
    
        sub     sp, sp, #0xf0;      //分配栈空间
        stp     X0, X1, [SP];       //存储x0-x29
        stp     X2, X3, [SP,#0x10]
        stp     X4, X5, [SP,#0x20]
        stp     X6, X7, [SP,#0x30]
        stp     X8, X9, [SP,#0x40]
        stp     X10, X11, [SP,#0x50]
        stp     X12, X13, [SP,#0x60]
        stp     X14, X15, [SP,#0x70]
        stp     X16, X17, [SP,#0x80]
        stp     X18, X19, [SP,#0x90]
        stp     X20, X21, [SP,#0xa0]
        stp     X22, X23, [SP,#0xb0]
        stp     X24, X25, [SP,#0xc0]
        stp     X26, X27, [SP,#0xd0]
        stp     X28, X29, [SP,#0xe0]
    
        mov     x0, sp;                 //x0作为第一个参数,那么操作x0=sp,即操作栈读写保存的寄存器
        ldr     x1, r_hk_info;
        ldr     x3, [x1, #0x20];        //aft_callback
        bl      get_lr_pc;              //lr为下条指令
        add     lr, lr, 8;              //lr为blr x3的地址
        str     lr, [sp, #0x108];        //lr当作pc,覆盖栈上的x0
        blr     x3;                     //执行aft_callback
    
    to_popreg:
        ldr     x0, [sp, #0x100];       //取出状态寄存器
        msr     NZCV, x0
    
        ldp     X0, X1, [SP];           //恢复x0-x29寄存器
        ldp     X2, X3, [SP,#0x10]
        ldp     X4, X5, [SP,#0x20]
        ldp     X6, X7, [SP,#0x30]
        ldp     X8, X9, [SP,#0x40]
        ldp     X10, X11, [SP,#0x50]
        ldp     X12, X13, [SP,#0x60]
        ldp     X14, X15, [SP,#0x70]
        ldp     X16, X17, [SP,#0x80]
        ldp     X18, X19, [SP,#0x90]
        ldp     X20, X21, [SP,#0xa0]
        ldp     X22, X23, [SP,#0xb0]
        ldp     X24, X25, [SP,#0xc0]
        ldp     X26, X27, [SP,#0xd0]
        ldp     X28, X29, [SP,#0xe0]
        add     sp, sp, #0xf0
    
        ldr     x30, [sp];              //恢复x30
        add     sp, sp, #0x20;          //恢复为真实sp
    
                                        //巧的是下条指令也是ret或者br lr,共用一条指令。
    
    get_lr_pc:
        ret;                            //仅用于获取LR/PC
    
    r_hk_info:                           //结构体STR_HK_INFO
    .double 0xffffffffffffffff
    
    r_dump_end:
    
    .end
    
    

    和dump不同,在原函数执行后可以对返回值进行读写,且可以只处理参数或者只处理返回值。

    因为要读写返回值,所以执行原函数之前需要修改lr寄存器,而读写返回值之后还要恢复正常的流程,那么lr寄存器是需要保存的。在这个shellcode或者结构体的一个字段存储lr都存在多线程覆盖的问题,所以使用和线程绑定的容器存储。那么何时保存呢?考虑到代码复用和最小的更改,那么可以在调用onPreCallBack函数内保存,但是这个函数是用户创建的,不应该让用户参与保存,而且这个onPreCallBack不一定存在。所以做一次中转,shellcode中先跳到一个一定存在的函数preCallBack,preCallBack内保存lr,并调用onPreCallBack(如果存在)。恢复lr也是同样的思路。

    //头文件
    
    #include <map>
    #include <pthread.h>
    #include <vector>
    
    typedef std::vector<unsigned long> LRS;
    //static LRS lrs;
    
    struct STR_LR {
    
    };
    typedef std::map<const void*, LRS*> LR_MAP;
    
    //typedef std::map<pid_t, LR_MAP*> TID_MAP;
    typedef std::map<pid_t, LR_MAP*> TID_MAP;
    
    static TID_MAP * getTid_map();
    
    static void saveLR(void* key_fun, unsigned long lr);
    
    static unsigned long getLR(void* key_fun);
    
    #endif
    
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    #include "mhk.h"
    
    //因为会被导出,所以两个静态库中存在两个同名函数,另一个被覆盖了。
    //extern void pre_callback(struct my_pt_regs *regs, HK_INFO* info);
    static void pre_callback(struct my_pt_regs *regs, HK_INFO* info);
    
    //extern void aft_callback(struct my_pt_regs *regs, HK_INFO* info);
    static void aft_callback(struct my_pt_regs *regs, HK_INFO* info);
    typedef void (*callback)(struct my_pt_regs *regs, HK_INFO* info);
    extern callback d_pre_callback;// = pre_callback;
    extern callback d_aft_callback;// = aft_callback;
    
    #ifdef __cplusplus
    }
    #endif
    
    //头文件结束
    
    static TID_MAP* tid_map;
    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
    TID_MAP * getTid_map(){
    //    pthread_mutex_init(&mutex, NULL);
        pthread_mutex_lock(&mutex);
        if (tid_map == NULL) {
            tid_map = new TID_MAP;
        }
        pthread_mutex_unlock(&mutex);
        return tid_map;
    }
    
    //因为不清楚tid分配机制,是否存在已死亡线程的tid重新分配给新建线程,所以可以ls -la /proc/8205/task/根据线程时间
    //判断是不是一个新线程,若是使用旧tid的新线程可以清空map,不过其实使用的栈结构存数据,理论上不区分也应该没问题的。
    void saveLR(void* key_fun, unsigned long lr){
        pid_t tid = gettid();
        TID_MAP *map = getTid_map();
        pthread_mutex_lock(&mutex);
        auto it = map->find(tid);
        if (it == map->end()) {
            auto lr_map = new LR_MAP;
            auto ls = new LRS;
            ls->push_back(lr);
            lr_map->insert(std::make_pair(key_fun, ls));
            map->insert(std::make_pair(tid, lr_map));
        } else {
            auto lr_map = it->second;
            auto it_vt = lr_map->find(key_fun);
            if (it_vt == lr_map->end()) {
                auto ls = new LRS;
                ls->push_back(lr);
                lr_map->insert(std::make_pair(key_fun, ls));
            } else {
                std::vector<unsigned long> *vt = it_vt->second;
                vt->push_back(lr);
            }
        }
        pthread_mutex_unlock(&mutex);
    }
    
    unsigned long getLR(void* key_fun){
        unsigned long lr = 0;
        pid_t tid = gettid();
        TID_MAP *map = getTid_map();
        pthread_mutex_lock(&mutex);
        auto it = map->find(tid);
        if (it == map->end()) {
            LE("what's happened ? not found tid=%d", tid);//理论上不应该出现
        } else {
            auto lr_map = it->second;
            auto it_vt = lr_map->find(key_fun);
            if (it_vt == lr_map->end()) {
                LE("what's happened ? not found LR for=%p in tid=%d", key_fun, tid);//理论上不应该出现
            } else {
                std::vector<unsigned long> *vt = it_vt->second;
                if (!vt || /*vt->size() <= 0*/ vt->empty()) {
                    LE("what's happened ? null LR for=%p in tid=%d", key_fun, tid);
                } else {
                    unsigned long size = vt->size();
                    lr = (*vt)[size - 1];
                    vt->pop_back();
                }
            }
        }
        pthread_mutex_unlock(&mutex);
        return lr;
    }
    
    void pre_callback(struct my_pt_regs *regs, HK_INFO* info){
        LE("dump_with_ret pre_callback");
        saveLR(info->pBeHookAddr, regs->ARM_lr);
        if (info->onPreCallBack)
            info->onPreCallBack(regs, info);
    }
    
    void aft_callback(struct my_pt_regs *regs, HK_INFO* info){
        LE("dump_with_ret aft_callback");
        unsigned long lr = getLR(info->pBeHookAddr);
        regs->ARM_lr = lr;
        if (info->onCallBack)
            info->onCallBack(regs, info);
    }
    
    
    callback d_pre_callback = pre_callback;
    callback d_aft_callback = aft_callback;
    
    

    目前是使用"c++"容器实现的,考虑到有些项目可能不能用c++,有时间再用c实现map、vector。

    dump_with_ret_demo

    void test_dump_with_ret(){
        LE("open=%p, callback=%p", open, onPreCallBack);
        if (dump((void *)(open), onPreCallBack, onCallBack, "open") != success) {
            LE("hook open error");
        }
    
        int fd = open("/system/lib/libc.so", O_RDONLY);
        LE("open /system/lib/libc.so, fd=%d", fd);
    }
    
    void test_dump_ret(){
        LE("open=%p, callback=%p", open, onPreCallBack);
        if (dump((void *)(open), NULL, onCallBack, "open") != success) {
            LE("hook open error");
        }
    
        int fd = open("/system/lib/libc.so", O_RDONLY);
        LE("open /system/lib/libc.so, fd=%d", fd);
    }
    
    

    代码实现_arm64_dump_just_ret函数

    .global j_dump_start
    .global j_dump_end
    .global j_hk_info
    
    .hidden j_dump_start
    .hidden j_dump_end
    .hidden j_hk_info
    
    
    .data
    
    j_dump_start:
    
        sub     sp, sp, #0x20;      //跳板在栈上存储了x0、x1,但是未改变sp的值
    
        mrs     x0, NZCV
        str     x0, [sp, #0x10];    //覆盖跳板存储的x1,存储状态寄存器
        str     x30, [sp];          //存储x30
        add     x30, sp, #0x20
        str     x30, [sp, #0x8];    //存储真实的sp
        ldr     x0, [sp, #0x18];    //取出跳板存储的x0
    
        sub     sp, sp, #0xf0;      //分配栈空间
        stp     X0, X1, [SP];       //存储x0-x29
        stp     X2, X3, [SP,#0x10]
        stp     X4, X5, [SP,#0x20]
        stp     X6, X7, [SP,#0x30]
        stp     X8, X9, [SP,#0x40]
        stp     X10, X11, [SP,#0x50]
        stp     X12, X13, [SP,#0x60]
        stp     X14, X15, [SP,#0x70]
        stp     X16, X17, [SP,#0x80]
        stp     X18, X19, [SP,#0x90]
        stp     X20, X21, [SP,#0xa0]
        stp     X22, X23, [SP,#0xb0]
        stp     X24, X25, [SP,#0xc0]
        stp     X26, X27, [SP,#0xd0]
        stp     X28, X29, [SP,#0xe0]
    
        mov     x0, sp;                 //x0作为第一个参数,那么操作x0=sp,即操作栈读写保存的寄存器
        ldr     x1, j_hk_info;
        ldr     x3, [x1];               //onPreCallBack
        bl      get_lr_pc;              //lr为下条指令
        add     lr, lr, 8;              //lr为blr x3的地址
        str     lr, [sp, #0x108];        //lr当作pc,覆盖栈上的x0
        blr     x3
    
    to_popreg:
        ldr     x0, [sp, #0x100];       //取出状态寄存器
        msr     NZCV, x0
    
        ldp     X0, X1, [SP];           //恢复x0-x29寄存器
        ldp     X2, X3, [SP,#0x10]
        ldp     X4, X5, [SP,#0x20]
        ldp     X6, X7, [SP,#0x30]
        ldp     X8, X9, [SP,#0x40]
        ldp     X10, X11, [SP,#0x50]
        ldp     X12, X13, [SP,#0x60]
        ldp     X14, X15, [SP,#0x70]
        ldp     X16, X17, [SP,#0x80]
        ldp     X18, X19, [SP,#0x90]
        ldp     X20, X21, [SP,#0xa0]
        ldp     X22, X23, [SP,#0xb0]
        ldp     X24, X25, [SP,#0xc0]
        ldp     X26, X27, [SP,#0xd0]
        ldp     X28, X29, [SP,#0xe0]
        add     sp, sp, #0xf0
    
        ldr     x30, [sp];              //恢复x30
        add     sp, sp, #0x20;          //恢复为真实sp
    
    
    get_lr_pc:
        ret;                            //仅用于获取LR/PC
    
    j_hk_info:                           //结构体STR_HK_INFO
    .double 0xffffffffffffffff
    
    j_dump_end:
    
    .end
    
    

    和dump不同,不再执行原函数,可以直接修改x0、x1寄存器返回值或者什么都不做。这是四种方式中最简单的一种。

    dump_just_ret_demo

    void test_dump_just_ret(){
        LE("open=%p, callback=%p", open, onPreCallBack);
        if (dumpRet((void *)(open), onCallBack, "open") != success) {
            LE("hook open error");
        }
    
        int fd = open("/system/lib/libc.so", O_RDONLY);
        LE("open /system/lib/libc.so, fd=%d", fd);
    }
    

    代码实现_arm64_replace函数

    .global replace_start
    .global replace_end
    .global p_hk_info
    
    .hidden replace_start
    .hidden replace_end
    .hidden p_hk_info
    
    .data
    
    //这种方式尽量用于标准的c/c++函数,因为通过hook函数再调用原函数,只能保证参数寄存器和lr寄存器是一致的,其他寄存器可能被修改。
    
    replace_start:                      //如果只是替换/跳到hook函数,其实是不用保存寄存器的,只是重新写比较麻烦,所以在之前的基础上
    
        sub     sp, sp, #0x20;      //跳板在栈上存储了x0、x1,但是未改变sp的值
    
        mrs     x0, NZCV
        str     x0, [sp, #0x10];    //覆盖跳板存储的x1,存储状态寄存器
        str     x30, [sp];          //存储x30
        add     x30, sp, #0x20
        str     x30, [sp, #0x8];    //存储真实的sp
        ldr     x0, [sp, #0x18];    //取出跳板存储的x0
    
        sub     sp, sp, #0xf0;      //分配栈空间
        stp     X0, X1, [SP];       //存储x0-x29
        stp     X2, X3, [SP,#0x10]
        stp     X4, X5, [SP,#0x20]
        stp     X6, X7, [SP,#0x30]
        stp     X8, X9, [SP,#0x40]
        stp     X10, X11, [SP,#0x50]
        stp     X12, X13, [SP,#0x60]
        stp     X14, X15, [SP,#0x70]
        stp     X16, X17, [SP,#0x80]
        stp     X18, X19, [SP,#0x90]
        stp     X20, X21, [SP,#0xa0]
        stp     X22, X23, [SP,#0xb0]
        stp     X24, X25, [SP,#0xc0]
        stp     X26, X27, [SP,#0xd0]
        stp     X28, X29, [SP,#0xe0]
    
        mov     x0, sp;                 //x0作为第一个参数,那么操作x0=sp,即操作栈读写保存的寄存器
        ldr     x1, p_hk_info;
        ldr     x3, [x1, #0x10];        //pre_callback,保存lr
        bl      get_lr_pc;              //lr为下条指令
        add     lr, lr, 8;              //lr为blr x3的地址
        str     lr, [sp, #0x108];        //lr当作pc,覆盖栈上的x0
        blr     x3
    
    to_call_hkFun:
        ldr     x0, [sp, #0x100];       //取出状态寄存器
        msr     NZCV, x0
    
        ldp     X0, X1, [SP];           //恢复x0-x29寄存器
        ldp     X2, X3, [SP,#0x10]
        ldp     X4, X5, [SP,#0x20]
        ldp     X6, X7, [SP,#0x30]
        ldp     X8, X9, [SP,#0x40]
        ldp     X10, X11, [SP,#0x50]
        ldp     X12, X13, [SP,#0x60]
        ldp     X14, X15, [SP,#0x70]
        ldp     X16, X17, [SP,#0x80]
        ldp     X18, X19, [SP,#0x90]
        ldp     X20, X21, [SP,#0xa0]
        ldp     X22, X23, [SP,#0xb0]
        ldp     X24, X25, [SP,#0xc0]
        ldp     X26, X27, [SP,#0xd0]
        ldp     X28, X29, [SP,#0xe0]
        add     sp, sp, #0xf0
    
        ldr     x30, [sp];              //恢复x30
        add     sp, sp, #0x20;          //恢复为真实sp
    
        ldr     lr, p_hk_info;
        ldr     lr, [lr, #0x28];           //pHkFunAddr
        blr     lr;                     //既跳到pHkFunAddr执行,也设置了lr
    
    to_aft_callback:                    //其实这里可能存在问题,即如果hook函数或者其调用了原函数,非标准函数(非c/c++)未实现栈平衡
                                        //比如手写的精心构造的汇编函数,可能存在覆盖栈上数据
        STP     X1, X0, [SP, #-0x10]
        sub     sp, sp, #0x20;      //跳板在栈上存储了x0、x1,但是未改变sp的值
    
        mrs     x0, NZCV
        str     x0, [sp, #0x10];    //覆盖跳板存储的x1,存储状态寄存器
        str     x30, [sp];          //存储x30
        add     x30, sp, #0x20
        str     x30, [sp, #0x8];    //存储真实的sp
        ldr     x0, [sp, #0x18];    //取出跳板存储的x0
    
        sub     sp, sp, #0xf0;      //分配栈空间
        stp     X0, X1, [SP];       //存储x0-x29
        stp     X2, X3, [SP,#0x10]
        stp     X4, X5, [SP,#0x20]
        stp     X6, X7, [SP,#0x30]
        stp     X8, X9, [SP,#0x40]
        stp     X10, X11, [SP,#0x50]
        stp     X12, X13, [SP,#0x60]
        stp     X14, X15, [SP,#0x70]
        stp     X16, X17, [SP,#0x80]
        stp     X18, X19, [SP,#0x90]
        stp     X20, X21, [SP,#0xa0]
        stp     X22, X23, [SP,#0xb0]
        stp     X24, X25, [SP,#0xc0]
        stp     X26, X27, [SP,#0xd0]
        stp     X28, X29, [SP,#0xe0]
    
        mov     x0, sp;                 //x0作为第一个参数,那么操作x0=sp,即操作栈读写保存的寄存器
        ldr     x1, p_hk_info;
        ldr     x3, [x1, #0x20];        //aft_callback, 恢复lr寄存器
        bl      get_lr_pc;              //lr为下条指令
        add     lr, lr, 8;              //lr为blr x3的地址
        str     lr, [sp, #0x108];        //lr当作pc,覆盖栈上的x0
        blr     x3;                     //执行aft_callback
    
    to_popreg:
        ldr     x0, [sp, #0x100];       //取出状态寄存器
        msr     NZCV, x0
    
        ldp     X0, X1, [SP];           //恢复x0-x29寄存器
        ldp     X2, X3, [SP,#0x10]
        ldp     X4, X5, [SP,#0x20]
        ldp     X6, X7, [SP,#0x30]
        ldp     X8, X9, [SP,#0x40]
        ldp     X10, X11, [SP,#0x50]
        ldp     X12, X13, [SP,#0x60]
        ldp     X14, X15, [SP,#0x70]
        ldp     X16, X17, [SP,#0x80]
        ldp     X18, X19, [SP,#0x90]
        ldp     X20, X21, [SP,#0xa0]
        ldp     X22, X23, [SP,#0xb0]
        ldp     X24, X25, [SP,#0xc0]
        ldp     X26, X27, [SP,#0xd0]
        ldp     X28, X29, [SP,#0xe0]
        add     sp, sp, #0xf0
    
        ldr     x30, [sp];              //恢复x30
        add     sp, sp, #0x20;          //恢复为真实sp
    
    get_lr_pc:
        br lr;                            //仅用于获取LR/PC,最后相当于br lr
        nop;
    
    p_hk_info:                           //结构体STR_HK_INFO
    .double 0xffffffffffffffff
    
    replace_end:
    
    .end
    

    和dump_with_ret大部分是一致的,执行原函数换成执行hook函数。因为这个hook函数不是shellcode,所以没有好办法在开头加入恢复寄存器的指令,那么就要使用一个无关的寄存器,因为要返回所以lr寄存器被保存了也改变了,所以这里就使用lr寄存器即可,blr lr,既跳到lr保存的地址去执行,也会把下条指令的地址存到lr。

    replace_demo

    typedef int (*old_open)(const char* pathname,int flags,/*mode_t mode*/...);
    typedef int (*old__open)(const char* pathname,int flags,int mode);
    typedef int (*old__openat)(int fd, const char *pathname, int flags, int mode);
    typedef FILE* (*old_fopen)(const char* __path, const char* __mode);
    
    old_open *ori_open;
    old__open *ori__open;
    
    //可变参数的函数,需要自己按照被hook函数的逻辑,解析出参数再传递给原函数。
    //因为并不清楚参数个数/类型,如果不改变参数的情况下还有方法不解析参数调用原函数。
    //但是如果改变了参数,比如printf中的fmt,那么理论上后面的参数类型个数也应该改变,这种情况下应该是使用者
    //已经清楚共用多少参数和类型,应该自己调用,而如果只改fmt,应该会出bug的。
    //所以如果只是想打印明确类型的参数,不改变参数直接调用原函数的情况,应该实现下参数的解析重组/传递,待实现
    int my_open(const char* pathname,int flags, .../*int mode*/){
    
        mode_t mode = 0;
    
        if (needs_mode(flags)) {
            va_list args;
            va_start(args, flags);
            mode = static_cast<mode_t>(va_arg(args, int));
            va_end(args);
        }
    
        LE("hk: open %s , %d, %d", pathname ? pathname : "is null", flags, mode);
    
    
        //测试解除hook
    //    HK_INFO *pInfo = isHookedByHkFun((void *) (my_open));
    //    unHook(pInfo);
    
        int fd = -1;
        if (!ori_open) {//理论上只有粗心没有给ori_open赋值。
            LE("ori_open null !");
            exit(1);
        }
    
        if (!(*ori_open)) {//理论上应该是hook取消了,但是如果是自己忘了给ori_open赋值或者赋值为NULL,那就是自己的锅了,太粗心了,会陷入死循环。
            LE("unhook open");
            old_open tmp = open;
            ori_open = &tmp;
        }
        fd = (*ori_open)(pathname, flags, mode);
        LE("ori: open %s , fd=%d", pathname ? pathname : "is null", fd);
        return fd;
    
    }
    
    
    void test_replace(){
        LE("open=%p, callback=%p", open, onPreCallBack);
        const RetInfo info = dump_replace((void *) (open), (void *) (my_open), onPreCallBack,
                                           onCallBack, "open");
        
        if (info.status != success) {
            LE("hook open error");
        } else {
        
            //考虑到解除hook的问题,不能用getOriFun直接获取备份的函数,应该获取函数的指针
            //不然直接返回函数,free函数后无法知道。建议不用自己保存原函数,使用api获取,当然如果没有unhook的情况,就不需要考虑这些问题。
            ori_open = (old_open*)(getPoriFun(info.info));
        }
    
        int fd = open("/system/lib/libc.so", O_RDONLY);
        LE("open /system/lib/libc.so, fd=%d", fd);
        unHook(info.info);
    }
    
    
    FILE* my_fopen(const char* pathname,const char* mode){
        LE("hk: fopen %s , %s", pathname ? pathname : "is null", mode);
    
        FILE* fd = NULL;
        //理论上有可能存在取消hook了,但是hook函数还要执行,所以应该可以再回调原函数了。
        //所以如果要绝对安全,那么hook和取消hook时暂停线程,检查函数调用栈是必须的。
        auto ori_open = (old_fopen)(getOriFunByHkFun((void *)(my_fopen)));
        if (!ori_open) {
            ori_open = (old_fopen)fopen;
        }
        fd = ori_open(pathname, mode);
        LE("ori: fopen %s , fd=%p", pathname ? pathname : "is null", fd);
        return fd;
    }
    
    
    
    //9.0上arm64未导出__open函数,而arm下实现为:
    //.text:0006FEDC
    //.text:0006FEDC                 EXPORT __open
    //.text:0006FEDC __open                                  ; DATA XREF: LOAD:0000254C↑o
    //.text:0006FEDC ; __unwind {
    //.text:0006FEDC                 PUSH            {R7,LR}
    //.text:0006FEDE                 BLX             j_abort
    //.text:0006FEDE ; } // starts at 6FEDC
    //.text:0006FEDE ; End of function __open
    
    //open函数不再走__open,而是__openat,9.0上arm64未导出__openat函数(只是隐藏了符号,可以自己根据字符串解析出地址),arm导出。
    void test_justReplace(){
        LE("open=%p, callback=%p", open, onPreCallBack);
    //    const RetInfo info = dump_replace(dlsym(RTLD_DEFAULT, "__openat"), (void *) (my__open), NULL,
    //                                       NULL, "__open");
        //android高版本没有__open了,__openat也隐藏符号了(只有64位隐藏了)
    //    const RetInfo info = dump_replace(dlsym(RTLD_DEFAULT, "__openat"), (void *) (my__openat), NULL,
    //                                       NULL, "__openat");
        
        const RetInfo info = dump_replace((void*)fopen, (void *) (my_fopen), NULL,
                                          NULL, "fopen");
        if (info.status != success) {
            LE("hook fopen error=%d", info.status);
        }
    
    
        FILE *pFile = fopen("/system/lib/libc.so", "re");
        LE("fopen /system/lib/libc.so, fd=%p", pFile);
        unHook(info.info);
    }
    
    

    api

    以上就是4种hook方式和对应的实现。提供给用户的api接口如下:

    #ifdef __cplusplus
    
    
    #include <map>
    #include <pthread.h>
    #include <vector>
    
    typedef std::vector<HK_INFO*> INFOS;
    //typedef std::map<const void*, HK_INFO*> LR_MAP;
    
    #endif
    
    enum hk_status{
        success, hooked, error
    };
    
    struct RetInfo {
        enum hk_status status;
        HK_INFO *info;
    };
    
    //打印寄存器
    /**
     *  用户自定义的stub函数,嵌入在hook点中,可直接操作寄存器等
     * @param regs      寄存器结构,保存寄存器当前hook点的寄存器信息
     * @param pInfo     保存了被hook函数、hook函数等的结构体
     */
    void default_onPreCallBack(my_pt_regs *regs, HK_INFO *pInfo);
    /**
     *  用户自定义的stub函数,嵌入在hook点中,可直接操作寄存器等
     * @param regs      寄存器结构,保存寄存器当前hook点的寄存器信息
     * @param pInfo     保存了被hook函数、hook函数等的结构体
     */
    void default_onCallBack(my_pt_regs *regs, HK_INFO *pInfo);
    
    /**
     * 真实函数执行前调用onPreCallBack,执行后调用onCallBack,通过onPreCallBack控制参数,通过onCallBack控制返回值
     * @param pBeHookAddr       要hook的地址,必须
     * @param onPreCallBack     要插入的回调函数(读写参数寄存器), 可以为NULL(onCallBack不为空),当和onCallBack都为NULL的情况使用默认的打印寄存器的函数default_onPreCallBack,因为什么都不做为什么hook?
     * @param onCallBack        要插入的回调函数(读写返回值寄存器),可以为NULL,如果只关心函数执行后的结果
     * @param methodName        被hook的函数名称,可以为NULL。
     * @return                  success:成功;error:错误;hooked:已被hook;
     */
    hk_status dump(void *pBeHookAddr, void (*onPreCallBack)(struct my_pt_regs *, HK_INFO *pInfo), void (*onCallBack)(struct my_pt_regs *, struct STR_HK_INFO *pInfo) = NULL, const char* methodName = NULL);
    
    /**
     *  不执行真实函数,直接操作寄存器,之后恢复寄存器返回,理论上也是可以在onCallBack其中执行真实函数的,但是需要自己封装参数,调用后自己解析寄存器
     * @param pBeHookAddr       要hook的地址,必须
     * @param onCallBack        要插入的回调函数(读写参数寄存器),必须
     * @param methodName        被hook的函数名称,可以为NULL。
     * @return                  success:成功;error:错误;hooked:已被hook;
     */
    hk_status dumpRet(void *pBeHookAddr, void (*onCallBack)(struct my_pt_regs *, HK_INFO *pInfo), const char* methodName = NULL);
    
    /**
     *  针对标准函数,最常用的hook接口。定义一个和被hook函数原型一致的函数接收处理参数,可直接返回或者调用被备份/修复的原函数
     * @param pBeHookAddr       要hook的地址,必须
     * @param pHkFunAddr        和被hook函数原型一致的函数,接收处理参数,可直接返回或者调用被备份/修复的原函数,必须
     * @param onPreCallBack     要插入的回调函数(读写参数寄存器), 可以为NULL
     * @param onCallBack        要插入的回调函数(读写返回值寄存器),可以为NULL,如果只关心函数执行后的结果
     * @param methodName        被hook的函数名称,可以为NULL
     * @return                  因为既要区分三种状态,还存在返回备份/修复的原函数的情况,使用结构体存储两个字段,参考demo。
     */
    RetInfo dump_replace(void *pBeHookAddr, void*pHkFunAddr, void (*onPreCallBack)(struct my_pt_regs *, HK_INFO *pInfo) = NULL, void (*onCallBack)(struct my_pt_regs *, struct STR_HK_INFO *pInfo) = NULL, const char* methodName = NULL);
    
    /**
     *  通过被hook函数获取数据结构体
     * @param pBeHookAddr       被hook函数
     * @return                  返回HK_INFO结构体
     */
    HK_INFO *isHooked(void* pBeHookAddr);
    
    /**
     *  通过hook函数获取数据结构体
     * @param hkFun     hook函数
     * @return          返回HK_INFO结构体
     */
    HK_INFO *isHookedByHkFun(void* hkFun);
    
    /**
     *  获取备份/修复的被hook函数,主要是不清楚结构体字段的用户或者透明指针的情况
     * @param info
     * @return          返回备份/修复的被hook函数
     */
    void* getOriFun(HK_INFO* info);
    
    /**
     *  获取备份/修复的被hook函数的指针,二级指针;可用于自己保存,推荐存在取消hook的情况下调用getOriFunByHkFun函数
     * @param info
     * @return          返回指向存储备份/修复的被hook函数的指针
     */
    void** getPoriFun(HK_INFO* info);
    
    /**
     *  通过hook函数获取被hook的函数
     * @param hkFun     hook函数
     * @return          返回被hook函数,如果被取消hook或者未被hook返回NULL
     */
    void* getOriFunByHkFun(void* hkFun);
    
    /**
     *  取消hook,释放shellcode/备份的原方法占用的空间并还原原方法
     * @param info      如果成功会释放这个结构体,所以之后这个结构体/指针不能再用
     * @return          取消成功true,否则false
     */
    bool unHook(HK_INFO* info);
    
    /**
     *  取消所有的hook
     * @return
     */
    bool unHookAll();
    

    arm/thumb实现和难点

    以上就是arm64的实现和定义的api接口。arm的只需要依葫芦画瓢即可,比arm64简单多了。

    shellcode统一使用arm

    shellcode理论上是arm还是thumb都没有关系,而且Android的编译链中的汇编器不支持大部分的32位Thumb-2指令(默认情况下),

    .code 16
    //.thumb //和上面效果一致
    //.force_thumb //强制thumb
    shell_code:
        push    {r0, r1, r2, r3} //r0=r0, r1=sp(push之前的sp), r2=r14/lr, r3=cpsr
        mrs     r0, cpsr
        str     r0, [sp, #0xC]
        str     r14, [sp, #8]
    

    第四条指令编译报错:
    Error: lo register required – `str r14,[sp,#8]’

    因为thumb16中能直接操作的寄存器是r0-r7;r8-r12、r14寄存器不能被str、ldr等,ldr pc寄存器也不行,但是可以ldr到r0-r7,再mov到sp寄存器。而thumb模式下的跳板0也是LDR PC, [PC, #0],是32位的thumb32/thumb-2指令,所以理论上armv5和以下的cpu应该是不支持指令的。

    因为shellcode中汇编指令超出了thumb16的寄存器范围,使用thumb32/thumb-2指令,汇编器不支持:

    Error: unexpected character `w' in type specifier
    Error: bad instruction `str.w r14,[sp,#8]'
    

    所以不用在Android.mk中指定LOCAL_ARM_MODE := arm,因为这样整个module都是arm指令、4字节,浪费了空间,所以默认就行。奇怪的是编译出的文件包含arm和thumb32,混合的。没有细看是什么情况,编译c/c++使用的汇编器不一致吗?

    呃,后来看到:经过Google工程师的提醒,对于ARM GCC的汇编器,在汇编文件最上面加入.syntax unified之后,Thumb-2 T3 encoding汇编也能正常使用了,比如:

    .syntax unified
    .text
    .align 4
    .globl helloThumb
    
    .thumb
    .thumb_func
    helloThumb:
    
        add.w    r0, r0, r1, lsl #2
        bx        lr
    

    所以在汇编文件中加入

    .syntax unified
    .force_thumb
    

    可以把操作r0-r7寄存器之外的指令写成,ldr.w、str.w、add.w等,也可以不修改,编译器会自动转成thumb32/thumb-2指令。

    编译后就是thumb32/thumb-2指令了,
    在这里插入图片描述
    因为这个shellcode变成thumb了,所以构建跳板shellcode时(LDR PC, [PC, #0]/hook thumb函数或者LDR PC, [PC, #-4]/hook arm函数,指令下面的地址要加1)。

    所以hook arm函数也可以构建thumb的shellcode,同样的thumb函数也可以构建arm的shellcode。但是为了方便、不考虑四字节对齐,干脆还是都用arm的shellcode吧,只要不指定为thumb(默认可能就是)、不指定.syntax unified就始终编译出来的就是arm的shellcode。毕竟没有那么严重的强迫症,也占不了多少空间。

    LDR PC, [PC, #0]/hook thumb函数或者LDR PC, [PC, #-4]/hook arm函数,理论上在armv5及以上就可以,所以无论是arm到thumb还是thumb到thumb,LDR到PC的地址+1就可以了。同样到arm就保证是非奇数的地址即可。
    在这里插入图片描述

    至于这个shellcode编译到.text还是.data都可以。不同的就是.data中是数据,.text中为函数。

    跳板0统一使用ldr pc

    只要cpu是armv5以上就行,因为目前很少armv5及以下的cpu了,所以不考虑支持问题。
    ldr pc, [pc, #0/#4],可被编译成arm或者thumb32/thumb-2指令。这里可能有人会有些误解,比如我们要hook的一个so是armeabi的,那么thumb函数只支持thumb16,所以是没有ldr pc这样的指令的,那么对于这样的函数还能使用ldr pc覆盖吗?其实是可以的,因为这条指令能不能被正确解析执行是取决于cpu,只要cpu支持thumb32/thumb-2指令(armv5以上的cpu)即可。thumb32/thumb-2指令更像是thumb16的一个超集(16位的指令应该都是相同的,32位的指令会不同),所以不影响后面的thumb16的指令的解析执行。

    所以这里我们不是对当前函数的thumb做兼容性适配而是针对cpu的,而如果要适配armv5以下,就多绕一下好了。

    代码实现_arm/thumb_统一的跳板0

    arm:

        LDR PC, [PC, #-4];流水线,pc为addr下面的指令,所以-4
        addr:0x12345678
    

    thumb32/thumb-2:

        LDR PC, [PC, #0];流水线,pc为addr指令
        addr:0x12345678
    

    都是占用8条指令,如果thumb函数地址不是4字节对齐的话就加nop或者bx #-2占用10字节。

    代码实现_arm_dump函数

    .global _dump_start
    .global _dump_end
    .global _hk_info
    .global _oriFuc
    
    .hidden _dump_start
    .hidden _dump_end
    .hidden _hk_info
    .hidden _oriFuc
    
    //可用于标准的c/c++函数、非标准函数、函数的一部分(用于读写寄存器),前提都是字节长度足够
    //非标准函数即非c/c++编译的函数,那么手写汇编可能存在并不遵守约定的情况,比如我们使用了sp寄存器,并在未使用的栈上保存寄存器
    //但是可能不是满递减而是反过来满递增,或者不遵守栈平衡,往栈上写数据,但是并不改变sp寄存器。当然应该是很少见的。
    
    .data
    
    _dump_start:                    //用于读写寄存器/栈,需要自己解析参数,不能读写返回值,不能阻止原函数(被hook函数)的执行
                                    //从行为上来我觉得更偏向dump,所以起名为dump。
        push    {r0-r4}                     //r0=r0,中转用 r1=sp(push之前的sp), r2=r14/lr, r3=pc, r4=cpsr
        mrs     r0, cpsr
        str     r0, [sp, #0x10]             //r4的位置存放cpsr
        str     r14, [sp, #8]               //r2的位置存放lr
        add     r14, sp, #0x14
        str     r14, [sp, #4]               //r1的位置存放真实sp
        pop     {r0}                        //恢复r0
        push    {r0-r12}                    //保存r-r12,之后是r13-r16/cpsr
        mov     r0, sp
        ldr     r1, _hk_info;
        ldr     r3, [r1];                   //onPreCallBack
        str     pc, [sp, #0x3c];            //存储pc,arm模式,所以pc+8,指向ldr     r0, [sp, #0x40]
        blx     r3
        ldr     r0, [sp, #0x40]             //cpsr
        msr     cpsr, r0
        ldmfd   sp!, {r0-r12}               //恢复r0-r12
        ldr     r14, [sp, #4]               //恢复r14/lr
        ldr     sp, [r13]                   //恢复sp(push之前的sp)
        ldr     pc, _oriFuc
    
    _oriFuc:                                //备份/修复的原方法
    .word 0x12345678
    
    _hk_info:                               //结构体STR_HK_INFO
    .word 0x12345678
    
    _dump_end:
    
    .end
    
    
    

    这里和arm64比有点不同的是多了一个标号/4个字节存放_oriFuc/原函数的地址,这么做的原因是比较无奈的,因为_hk_info取到的是HK_INFO,还需要再计算一次才能得到pOriFuncAddr,这样就需要占用一个寄存器去计算,pc寄存器是不能这么用的,所以还是在shellcode中存储吧。当然其实还可以让_hk_info存放的是pOriFuncAddr这个变量的地址,那么可以一次取到pc寄存器中,但是其他shellcode都要变且不如原来的直观/简单了,同样的方式还可以把pOriFuncAddr放在结构体头部,然后_hk_info存放pOriFuncAddr变量的地址/HK_INFO结构体的地址,也是可以的,但是也是要变动太多。而且其实只有这一个shellcode需要多定义一个_oriFuc,其他是不需要的,所以折衷后就这样了。

    demo和arm64一致,api也是一致的。arm和thumb共用这一个shellcode,注意的一点就是如果被hook的函数是thumb,那么_oriFuc存放的地址+1即可。

    代码实现_arm_dump_with_ret函数

    
    .global r_dump_start
    .global r_dump_end
    .global r_hk_info
    
    .hidden r_dump_start
    .hidden r_dump_end
    .hidden r_hk_info
    
    
    .data
    
    r_dump_start:                    //用于读写寄存器/栈,需要自己解析参数,不能读写返回值,不能阻止原函数(被hook函数)的执行
                                    //从行为上来我觉得更偏向dump,所以起名为dump。
        push    {r0-r4}                     //r0=r0,中转用 r1=sp(push之前的sp), r2=r14/lr, r3=pc, r4=cpsr
        mrs     r0, cpsr
        str     r0, [sp, #0x10]             //r4的位置存放cpsr
        str     r14, [sp, #8]               //r2的位置存放lr
        add     r14, sp, #0x14
        str     r14, [sp, #4]               //r1的位置存放真实sp
        pop     {r0}                        //恢复r0
        push    {r0-r12}                    //保存r-r12,之后是r13-r16/cpsr
        mov     r0, sp
        ldr     r1, r_hk_info;
        ldr     r3, [r1, #8];               //pre_callback
        str     pc, [sp, #0x3c];            //存储pc
        blx     r3
    
    to_call_oriFun:
        ldr     r0, [sp, #0x40]             //cpsr
        msr     cpsr, r0
        ldmfd   sp!, {r0-r12}               //恢复r0-r12
        ldr     r14, [sp, #4]               //恢复r14/lr
        ldr     sp, [r13]                   //恢复sp(push之前的sp)
        ldr     lr, r_hk_info
        ldr     lr, [lr, #4];               //pOriFuncAddr
        blx     lr;
    
    to_aft_callback:
        push    {r0-r4}                     //r0=r0,中转用 r1=sp(push之前的sp), r2=r14/lr, r3=pc, r4=cpsr
        mrs     r0, cpsr
        str     r0, [sp, #0x10]             //r4的位置存放cpsr
        str     r14, [sp, #8]               //r2的位置存放lr
        add     r14, sp, #0x14
        str     r14, [sp, #4]               //r1的位置存放真实sp
        pop     {r0}                        //恢复r0
        push    {r0-r12}                    //保存r-r12,之后是r13-r16/cpsr
        mov     r0, sp
        ldr     r1, r_hk_info;
        ldr     r3, [r1, #0x10];            //aft_callback
        str     pc, [sp, #0x3c];            //存储pc
        blx     r3
    
    to_popreg:
        ldr     r0, [sp, #0x40]             //cpsr
        msr     cpsr, r0
        ldmfd   sp!, {r0-r12}               //恢复r0-r12
        ldr     r14, [sp, #4]               //恢复r14/lr
        ldr     sp, [r13]                   //恢复sp(push之前的sp)
        mov     pc, lr;
    
    r_hk_info:                               //结构体STR_HK_INFO
    .word 0x12345678
    
    r_dump_end:
    
    .end
    
    

    demo和arm64一致,这里不用多定义一个_oriFuc,因为保存了lr,不是必须使用pc寄存器了。也是让lr寄存器做了很多事情。

    代码实现_arm_dump_just_ret函数

    .global j_dump_start
    .global j_dump_end
    .global j_hk_info
    
    .hidden j_dump_start
    .hidden j_dump_end
    .hidden j_hk_info
    
    
    .data
    
    j_dump_start:
    
        push    {r0-r4}                     //r0=r0,中转用 r1=sp(push之前的sp), r2=r14/lr, r3=pc, r4=cpsr
        mrs     r0, cpsr
        str     r0, [sp, #0x10]             //r4的位置存放cpsr
        str     r14, [sp, #8]               //r2的位置存放lr
        add     r14, sp, #0x14
        str     r14, [sp, #4]               //r1的位置存放真实sp
        pop     {r0}                        //恢复r0
        push    {r0-r12}                    //保存r-r12,之后是r13-r16/cpsr
        mov     r0, sp
        ldr     r1, j_hk_info;
        ldr     r3, [r1];                   //onPreCallBack
        str     pc, [sp, #0x3c];            //存储pc
        blx     r3
        ldr     r0, [sp, #0x40]             //cpsr
        msr     cpsr, r0
        ldmfd   sp!, {r0-r12}               //恢复r0-r12
        ldr     r14, [sp, #4]               //恢复r14/lr
        ldr     sp, [r13]                   //恢复sp(push之前的sp)
        mov     pc, lr
    
    
    j_hk_info:                               //结构体STR_HK_INFO
    .word 0x12345678
    
    j_dump_end:
    
    .end
    
    

    demo和arm64一致。

    代码实现_arm_replace函数

    .global replace_start
    .global replace_end
    .global p_hk_info
    
    .hidden replace_start
    .hidden replace_end
    .hidden p_hk_info
    
    .data
    
    //这种方式尽量用于标准的c/c++函数,因为通过hook函数再调用原函数,只能保证参数寄存器和lr寄存器是一致的,其他寄存器可能被修改。
    
    replace_start:                      //如果只是替换/跳到hook函数,其实是不用保存寄存器的,只是重新写比较麻烦,所以在之前的基础上
    
        push    {r0-r4}                     //r0=r0,中转用 r1=sp(push之前的sp), r2=r14/lr, r3=pc, r4=cpsr
        mrs     r0, cpsr
        str     r0, [sp, #0x10]             //r4的位置存放cpsr
        str     r14, [sp, #8]               //r2的位置存放lr
        add     r14, sp, #0x14
        str     r14, [sp, #4]               //r1的位置存放真实sp
        pop     {r0}                        //恢复r0
        push    {r0-r12}                    //保存r-r12,之后是r13-r16/cpsr
        mov     r0, sp
        ldr     r1, p_hk_info;
        ldr     r3, [r1, #8];               //pre_callback,保存lr
        str     pc, [sp, #0x3c];            //存储pc
        blx     r3
    
    to_call_oriFun:
        ldr     r0, [sp, #0x40]             //cpsr
        msr     cpsr, r0
        ldmfd   sp!, {r0-r12}               //恢复r0-r12
        ldr     r14, [sp, #4]               //恢复r14/lr
        ldr     sp, [r13]                   //恢复sp(push之前的sp)
        ldr     lr, p_hk_info
        ldr     lr, [lr, #0x14];            //pHkFunAddr
        blx     lr;
    
    to_aft_callback:
        push    {r0-r4}                     //r0=r0,中转用 r1=sp(push之前的sp), r2=r14/lr, r3=pc, r4=cpsr
        mrs     r0, cpsr
        str     r0, [sp, #0x10]             //r4的位置存放cpsr
        str     r14, [sp, #8]               //r2的位置存放lr
        add     r14, sp, #0x14
        str     r14, [sp, #4]               //r1的位置存放真实sp
        pop     {r0}                        //恢复r0
        push    {r0-r12}                    //保存r-r12,之后是r13-r16/cpsr
        mov     r0, sp
        ldr     r1, p_hk_info;
        ldr     r3, [r1, #0x10];            //aft_callback
        str     pc, [sp, #0x3c];            //存储pc
        blx     r3
    
    to_popreg:
        ldr     r0, [sp, #0x40]             //cpsr
        msr     cpsr, r0
        ldmfd   sp!, {r0-r12}               //恢复r0-r12
        ldr     r14, [sp, #4]               //恢复r14/lr
        ldr     sp, [r13]                   //恢复sp(push之前的sp)
        mov     pc, lr;
    
    p_hk_info:                               //结构体STR_HK_INFO
    .word 0x12345678
    
    
    replace_end:
    
    .end
    

    demo和arm64一致。

    细节的优化

    框架已经搭好了且已经可以运行了,接下来就是一些细节的优化。

    内存对齐

    shellcode、备份/修复的原函数不能使用malloc等非内存对齐的函数,原因在下面的Android_Inline_Hook中较严重的一些bug有提到。但是每个shellcode都占用一页内存也太浪费了,理想情况是一页用完或者放不下一个shellcode再申请,但是考虑到解除hook释放shellcode等问题,这么做实现起来很复杂,要监听整个流程,暂时没精力写这么完美,所以就把shellcode和备份/修复的原函数放在同一页内存,当然还是用不满,但是至少节约了一些,这样解除hook直接释放这页内存都可以,当然还可以进一步把HK_INFO这个结构体也放在这一页内,但是不确定是否确实有些系统只允许内存权限为rw或者rp,暂时没有把HK_INFO混在一起。

    刷新指令缓存

    void test_args_11(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11){
        LE("a0=%d, a1=%d, a2=%d, a3=%d, a4=%d, a5=%d,"
           " a6=%d, a7=%d, a8=%d, a9=%d, a10=%d. a11=%d",
           a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11);
    }
    
    void nullCallBack(my_pt_regs *regs, HK_INFO *pInfo){
    
    }
    
    void test_args_for_cache(){
        //测试多参数传递情况
        dump((void *)(test_args_11), nullCallBack, /*onCallBack*/NULL);
    //    dumpRet((void *) (test_args_11), onPreCallBack, "test_args_11");
        test_args_11(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
        HK_INFO *pInfo = isHooked((void*)test_args_11);
    //    unHook(pInfo);
    }
    
    //拷贝回原指令后不刷新缓存。机器不同、cpu性能、缓存大小不同等,这个阈值需要自己测试。
    //性能好的机器,循环100次基本都可以100%复现,差的机器200次也只有几次复现。
    void test_cache(){
        test_args_for_cache();
        LE("test_args_11=%p", test_args_11);
        for (int i = 0; i < 188; ++i) {
            if (i == 178) {
                HK_INFO *pInfo = isHooked((void *) test_args_11);
                unHook(pInfo);
            }
            test_args_11(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
            LE("i=%d", i);
    //        usleep(1);
        }
        LE("end test cache");
    }
    

    为了测试不刷新缓存的情况,使用了循环,因为实测发现想触发缓存不刷新crash概率太低了。我能想到的就是循环执行一段指令,触发jit,提高指令被缓存的概率。然后修改指令后不刷新缓存,触发crash。关于如何刷新缓存在下面的Android_Inline_Hook中较严重的一些bug有提到。

    既是入参也是出参的函数

    只是确认下,因为代码写的没问题,所以应该是不会有问题的。

    int (*ori__system_property_get)(const char *name, char *value);
    
    //注意操作参数之前最好都检查参数是否为null,如果原函数也是crash处理还行,如果不是就改变了函数逻辑、进程中断等
    //所以细节都要注意,参数和返回值也有对应关系
    int my__system_property_get(const char *name, char *value){
        LE("hk: __system_property_get(%s, %p)", name ? name : "is null", value);
        ori__system_property_get = (int (*)(const char *, char *))(getOriFunByHkFun(
                (void *) (my__system_property_get)));
        if (!ori__system_property_get) {
            ori__system_property_get = __system_property_get;
        }
        int ret = ori__system_property_get(name, value);
        LE("ori: __system_property_get(%s, %s)=%d", name ? name : "is null", ret > 0 ? value : "is null", ret);
        if (name && !strncmp("ro.serialno", name, strlen("ro.serialno"))) {
            strcpy(value, "12345678");
            return (int)(strlen("12345678"));
        }
        return ret;
    }
    
    //测试既是参数也做返回值的函数
    void test__system_property_get() {
        const RetInfo info = dump_replace((void*)__system_property_get, (void *) (my__system_property_get), NULL,
                                          NULL, "__system_property_get");
        if (info.status != success) {
            LE("hook __system_property_get error=%d", info.status);
        }
    
        char sn[256];
        int ret = __system_property_get("ro.serialno", sn);
        LE("serialno=%s", ret > 0 ? sn : "is null");
    
        //2019-12-23 17:10:14.810 22213-22213/? E/zhuo: hk: __system_property_get(ro.serialno, 0x7fd55a2088)
        //2019-12-23 17:10:14.810 22213-22213/? E/zhuo: ori: __system_property_get(ro.serialno, 8acd10de)=8
        //2019-12-23 17:10:14.810 22213-22213/? E/zhuo: lr aft_callback
        //2019-12-23 17:10:14.810 22213-22213/? E/zhuo: serialno=12345678
    
    }
    

    测试不存在hookzz的问题。

    todo

    基本上上面已经是大部分的框架和部分细节,剩下的对用户api的具体实现都是很灵活的,只要保持和api函数原型和定义行为一致即可(已实现)。

    跳板、shellcode、api都实现了,还剩下一个对备份的原函数进行修复。还没有重新开始写,暂时用的Android_Inline_Hook修复pc相关的指令。在写arm的vmp,到时抽取出指令解析和生成的代码就好了,加一些简单的语义分析。

    线程暂停的问题,抽时间加上,因为一般自己常用的场景是fork一个进程还未执行应用代码的时候,基本上不会出现多线程导致的crach,不过还是应该提供接口,毕竟有些场景需要,但是怕是也不能百分百解决线程问题,还需要用户自己判断函数行为。

    可变参数的传递,如果不修改只是调用原函数倒是有思路了,但是如果是对明确的参数进行修改了,还是要抽时间写解析构造参数的部分。

    已知bug

    1、arm64指令修复

    .text:0000000000000EF8 dlopen                                  ; DATA XREF: LOAD:0000000000000508↑o
    .text:0000000000000EF8
    .text:0000000000000EF8 var_s0          =  0
    .text:0000000000000EF8
    .text:0000000000000EF8 ; __unwind {
    .text:0000000000000EF8                 STP             X29, X30, [SP,#-0x10+var_s0]!
    .text:0000000000000EFC                 MOV             X29, SP
    .text:0000000000000F00                 MOV             X2, X30
    .text:0000000000000F04                 BL              .__loader_dlopen
    .text:0000000000000F08                 LDP             X29, X30, [SP+var_s0],#0x10
    .text:0000000000000F0C                 RET
    .text:0000000000000F0C ; } // starts at EF8
    .text:0000000000000F0C ; End of function dlopen
    

    例如其中的BL指令并未修复,这个其实还容易修复,可以替换成LDR LR, 8(应该放到最后);BLR LR;因为既然是BL指令那么LR寄存器肯定是会被修改,那就说明LR寄存器已经保存了,所以我们就使用LR寄存器也是没有关系的。

    如果是B指令才麻烦,因为不能操作pc,那么就要至少使用一个寄存器且还不能是LR,只能使用x16、x17了。

    后面看了下Android_Inline_Hook写了B指令的修复(是错的),BL指令未实现,后来自己写了BL指令的实现,只是暂时用,不准备在这个基础上再写了,缺少的待修复的指令还有不少,且已实现的也有一些错误,待重构。

    在这里插入图片描述
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bidLFS27-1577937079578)(https://i.imgur.com/tqVxVK2.png)]

    修复之后Android9.0 arm64的dlopen可以hook了。

    2、arm64从备份/修复的原方法跳回跳板0之后执行,采用的方式是在备份/修复的原方法后面加入:

        STP X1, X0, [SP, #-0x10]
        LDR X0, 8
        BR X0
        ADDR(64)
        LDR X0, [SP, -0x8]
    

    和跳板0是一样的,但是跳板0是一个函数的开头的概率很大,所以一般都是会栈平衡的,sp指向栈顶,所以暂时存放寄存器不会被覆盖。而放在后面就不太确定能保证sp指向栈顶了,所以可能要考虑解析前几条指令是否有操作sp,或者尽可能的把寄存器储存在sp-较大的值,或者使用x16、x17寄存器。。。
    当然这个可能概率较小,可以先不管。

    3、无法重复hook一个函数,当然这个是说在其他框架或者本框架的另一个版本(或者无法维护保存hook的容器,如果是本框架内是可以重复hook同一个函数的,比如解除hook再hook,或者再扩展成链表的形式,依次调用hook/dump函数),其实就是指令修复不完整,arm的应该是ldr pc没有正确修复,这个事情排在后面,不是特别重要。

    也可以参考Cydia Substrate框架,不过有点坑,只处理thumb的情况,thumb可以多次hook,arm下第二次hook不生效。直接判断第一条指令是不是ldr pc, [pc, #-4];是的话就认为是已经被hook过的,直接返回。

        if (result != NULL) {
    
        if (backup[0] == A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8)) {
            *result = reinterpret_cast<void *>(backup[1]);
            return;
        }
    

    可见只有第一次hook生效。
    在这里插入图片描述

    而thumb模式,把上次的shellcode赋给result,再把跳板中的地址替换为新的地址。

    f (
            (align == 0 || area[0] == T$nop) &&
            thumb[0] == T$bx(A$pc) &&
            thumb[1] == T$nop &&
            arm[0] == A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8)
        ) {
            if (result != NULL)
                *result = reinterpret_cast<void *>(arm[1]);
    
            SubstrateHookMemory code(process, arm + 1, sizeof(uint32_t) * 1);
    
            arm[1] = reinterpret_cast<uint32_t>(replace);
    
            return sizeof(arm[0]);
        }
    

    在这里插入图片描述

    感觉是不是写代码的忘了处理arm的,因为arm也是可以同样处理的,呃,无语的bug。
    例如这么修复即可,前面再加一行SubstrateHookMemory code(process, symbol, used);修改权限/刷新缓存。
    在这里插入图片描述
    应该VirtualApp hook系统函数之后使用syscall调用,就是因为梆梆等一些壳hook了函数,而VirtualApp使用Cydia Substrate未修复该bug,所以导致hook失败。但是一推理应该是错误的,VirtualApp是先hook的,壳应该是后hook的,所以只能是壳hook失败,所以应该是某些系统函数修复指令不正确导致调用原函数失败,和本篇其实没多大关系,只是推理下。

    其实以上bug根本原因还是指令的修复,这是最麻烦的,也是暂时没办法达到百分百修复的,尤其是arm64,只能抽时间逐步完善了。

    Android_Inline_Hook中较严重的一些bug

    1、在init_arry函数和普通函数引用vector,但其实不是同一个vector的,导致无法保存已hook的信息,应该用指针,不能直接引用vector。属于代码逻辑bug。

    2、内存权限问题
    虽然mprotect的时候取一页内存,但是因为使用的malloc申请的内存可能是夹缝中内存,前后被使用了?也可能是申请后剩余的被其他占用更改了权限?虽然mprotect不报错,修改权限成功了,但是有几率signal 11 (SIGSEGV), code 2 (SEGV_ACCERR)。还要一种可能是malloc返回的地址是奇数的,未测试,理论上如果是奇数的也会crash的,因为内存没有对齐。
    所以使用

            long pagesize = sysconf(_SC_PAGE_SIZE);
            void *pNewShellCode = NULL;// = malloc(sShellCodeLength);
            int code = posix_memalign(&pNewShellCode,pagesize, pagesize);
    

    或者

        mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
    

    申请一整页的内存,但是这样肯定是浪费的,所以之后应该在这个内存上填充,占满之后再申请新的一页内存。

    3、修改内存权限,ChangePageProperty中如果超过一页的内存,第二页和之后的都没有设置权限,属于代码逻辑bug,但是没有超过2页的情况,所以不会触发。

    4、arm刷新缓存错误/未生效,arm64未刷新缓存。
    //通过测试:cacheflush无法刷新缓存,不过不确定是不是第三个参数的问题,ICACHE, DCACHE, or BCACHE,抽时间再看吧,
    //不过gtoad的把地址转成无符号指针再取值肯定是不正确的。
    // cacheflush(((uint32_t)(info->pBeHookAddr)), info->backUpLength, 0);//测试也无法刷新缓存
    // cacheflush(PAGE_START((uintptr_t)info->pBeHookAddr), PAGE_SIZE, 0);//测试也无法刷新缓存
    // cacheflush(info->pBeHookAddr, info->backUpLength, 0);//测试也无法刷新缓存

    //而确实有效的刷新缓存的是:可以直接传地址和结束地址,也可以刷新一页,效果来看都成功了。
    // if(TEST_BIT0((uint32_t)info->pBeHookAddr)){
    // __builtin___clear_cache((char*)info->pBeHookAddr - 1, (char*)info->pBeHookAddr - 1 + info->backUpLength);
    // } else {
    // __builtin___clear_cache((char *) info->pBeHookAddr,
    // (char *) info->pBeHookAddr + info->backUpLength);
    // }
    // __builtin___clear_cache(PAGE_START((uintptr_t)info->pBeHookAddr), PAGE_END((uintptr_t)info->pBeHookAddr + info->backUpLength));

    //后记:
    //测试cacheflush可以,但是和int cacheflush(long __addr, long __nbytes, long __cache);不一致的是,从命名来看第二个参数应该是大小,不应该是
    //结束地址,而且linux man中Some or all of the address range addr to (addr+nbytes-1) is not accessible。哎奇怪,抽时间再逆向看看吧。
    /if(TEST_BIT0((uint32_t)info->pBeHookAddr)){
    cacheflush((char
    )info->pBeHookAddr - 1, (char*)info->pBeHookAddr - 1 + info->backUpLength, 0);
    } else {
    cacheflush((char *) info->pBeHookAddr,
    (char ) info->pBeHookAddr + info->backUpLength, 0);
    }
    /

    //目前来看使用__builtin___clear_cache吧,应该是都实现了且确实可用,如果某些系统不生效在抠出系统调用吧,先使用函数吧。

    写在后面

    为了避免不必要的纠纷,前面列举的框架只是为了引出为什么在写一个的原因,没有其他的意思。只是想写一个让大多数人更容易使用、了解实现细节、能更容易参与开发完善的Android inline hook项目。所以暂时不考虑代码的艺术,而尽量的写成易懂的代码,且代码写了较多、完整的注释。

    也是希望不止是为了使用,而能让使用者能自己了解原理,自己就能解决错误,这方面感谢下Android_Inline_Hook。之前也写过一些自己个人用的临时hook,但是时间久了代码都找不到了,且也都是最简单的实现,临时用下的,所以也打算重新实现下,没时间一直拖着,看到Android_Inline_Hook,发现很多思路和我之前是一样的,代码也易懂,看一下就全明白了,所以一开始在Android_Inline_Hook上面修了一些bug凑合用,不过后来修的多了发现不如重新实现,因为很多地方不兼容、架构有冲突了。

    整理下,最近开源了,希望各位大佬多参与,完善修复bug,多谢!
    https://github.com/zhuotong/Android_InlineHook

    展开全文
  • 一个vc的inlineHook MessageBox小例子,希望对大家有帮助。
  • 发布一个自己的用于Hook .Net方法的类库,代码量不大,完全的C#代码实现,是一个比较有趣的功能,分享出来希望能和大家共同探讨 安装:Install-Package DotNetDetour 源码:...

    发布一个自己写的用于Hook .Net方法的类库,代码量不大,完全的C#代码实现,是一个比较有趣的功能,分享出来希望能和大家共同探讨

    安装:Install-Package DotNetDetour
    源码:https://github.com/bigbaldy1128/DotNetDetour

    1.为何想做这个
    说到hook大家都应该不陌生,就是改变函数的执行流程,让本应该执行的函数跑到另一个函数中执行,这是个很有用也很有趣的功能(例如获取函数参数信息,改变函数执行流程,计算函数执行时间等等),杀软中主防的原理就是hook,通过hook拦截函数获取参数信息来判断是否是危险行为,但这类程序大多是C++的,一直以来我都想实现可以hook .net函数的库,网上搜索了很多,但都不理想,所以想自己实现一个

    2.实现原理
    我采用的是inline hook的方式,因为我对.net虚拟机以及一些内部的结构并不是很熟悉,并且有些东西的确找不到任何文档,所以就采用原生代码的inline hook的方式来实现。

    首先说一下inline hook的基本原理,它是通过修改函数的前5字节指令为jmp xxxxxxxx来实现的,例如一个C#方法:

    用windbg调试查看方法信息:

    查看已经jit了的原生代码:

    这里的地址(0x008c0640)可以通过MethodInfo.MethodHandle.GetFunctionPointer().ToPointer()方法获取

    到了这里,我们就知道了修改从push ebp开始的5个字节为jmp跳转指令,跳入我们自己的函数就可以达到hook的目的,但执行到我们的函数后,如果我们并不是要拦截执行流程,那么我们最终是需要再调用原函数的,但原函数已经被修改了,这会想到的办法就是恢复那修改的5字节指令,但这又会引发另一个问题,就是当我们恢复时,正好另一个线程调用到这个函数,那么程序将会崩溃,或者说漏掉一次函数调用,修改时暂停其他线程并等待正跑在其中的CPU执行完这5字节再去恢复指令也许是个不错的办法,但感觉并不容易实现,而且影响性能,所以我放弃了这种办法

    那么如何才能调用修改前的函数呢,我首先想到是C中写裸函数的方式,即自己用汇编拼出来一个原函数再执行:
    原函数前5字节指令+jmp跳转指令
    但其实这也是不可行的,聪明的人已经发现,图中所示的函数的前5字节并不是一个完整的汇编指令,不同的函数,长度都不一样,.net的函数并不像某些原生函数那样,会预留mov edi,edi这样的正好5字节的指令,我先想到的是复制函数的所有汇编指令生成新的函数,但这样也会出问题,因为像E8,E9这样的相对跳转指令,如果指令地址变了,那么跳转的位置也就变了,程序就会崩溃,所以这也不可行。

    到了这里,我有些不耐烦了,毕竟我是要hook所有函数的,而不是某个固定的函数,而函数入口的指令又不相同,这可怎么办,难道我需要计算出大于等于5字节的最小完整汇编指令长度?

    按照这个思路,最终找到了一个用C写的反汇编库(BlackBone),其中提供了类似的方法,我稍作了修改后试用了下,的确不错,可以准确求出汇编指令长度,例如

    push ebp
    mov ebp,esp
    mov eax,dword ptr ds:[33F22ACh]

    求出值是9,这样我根据求出的值动态拼接一个函数出来即可,哈哈,到了这里,感觉实现的差不多了,但没想到64位下又给了我当头一棒,之前的原函数指令可以写成:

    大于等于5字节的最小完整汇编指令+jmp跳转指令即可构成我们的原函数

    但我们知道,C#中要想执行汇编,是需要用Marshal.AllocHGlobal来分配非托管空间的,而这样分配的地址与我们要跳转到的原函数的地址在64位下是超过2GB地址范围的,一般的跳转指令是无法实现的,所以想到了用ret指令实现,而64位地址又不能直接push,所以最后写出如下汇编:

    push rax
    mov rax,target_addr
    push rax
    mov rax,qword ptr ss:[rsp+8]
    ret 8

    由于某些C#函数竟然第一行就是修改rax寄存器的值,所以只能是先保存rax,推入堆栈后再恢复,这里汇编操作就方便多了,之前实现另一个东西,用到IL指令,但发现只有dup这种复制栈顶元素的指令,却没有获取堆栈中某个非栈顶元素值的指令,所以说还是汇编灵活啊,想怎么写就怎么写,啥都能实现。

    最后就是这个原函数的调用过程了,因为是动态拼接的函数,所以想到的就是用Marshal.GetDelegateForFunctionPointer转成委托来执行,后来发现不对,因为我虽然拼接的是汇编,而这个汇编是C#方法jit后的汇编,这个并不是C方法编译后的汇编,通过把非托管指针转换为委托的方式运行函数是会添加很多不需要的操作的,例如托管类型与非托管类型的转换,但我拼接出的函数是不需要这些过程的,这个怎么办,看来只能用调用C#普通函数的方式调用,这个怎么实现呢,其实很好办,只需写一个空壳函数,然后修改这个函数的方法表中的原生指令指针即可,具体方法如下:

    C# code?

    1

    *((ulong*)((uint*)method.MethodHandle.Value.ToPointer() + 2)) = (ulong)ptr;


    method是空壳函数的MethodInfo,         ptr是动态拼接的原函数的地址

    好,到了这里就基本完成核心功能了,最不好处理的就是这个原函数调用,我的完整的64位原函数指令拼接就实现了,代码很少,如下所示:

    C# code?

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    byte[] jmp_inst =

    {

        0x50,                                              //push rax

        0x48,0xB8,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90, //mov rax,target_addr

        0x50,                                              //push rax

        0x48,0x8B,0x44,0x24,0x08,                          //mov rax,qword ptr ss:[rsp+8]

        0xC2,0x08,0x00                                     //ret 8

    };

     

    protected override void CreateOriginalMethod(MethodInfo method)

    {

        uint oldProtect;

        var needSize = NativeAPI.SizeofMin5Byte(srcPtr);

        byte[] src_instr = new byte[needSize];

        for (int i = 0; i < needSize; i++)

        {

            src_instr[i] = srcPtr[i];

        }

        fixed (byte* p = &jmp_inst[3])

        {

            *((ulong*)p) = (ulong)(srcPtr + needSize);

        }

        var totalLength = src_instr.Length + jmp_inst.Length;

        IntPtr ptr = Marshal.AllocHGlobal(totalLength);

        Marshal.Copy(src_instr, 0, ptr, src_instr.Length);

        Marshal.Copy(jmp_inst, 0, ptr + src_instr.Length, jmp_inst.Length);

        NativeAPI.VirtualProtect(ptr, (uint)totalLength, Protection.PAGE_EXECUTE_READWRITE, out oldProtect);

        RuntimeHelpers.PrepareMethod(method.MethodHandle);

        *((ulong*)((uint*)method.MethodHandle.Value.ToPointer() + 2)) = (ulong)ptr;

    }



    3.类库开发所用到的语言
    之前我说,我的这个库是完全用C#实现的,但其中的确用到了一个C写的反汇编库,于是我用C#把那个库重写了一遍,说来也简单,C的代码粘过来,C#启用unsafe代码,改了10分钟就好了,真心是非常方便,毕竟C#是支持指针和结构体的,而且基础类型非常丰富,这里得给C#点个赞!

    4.具体使用
    使用非常简单,首先新建控制台程序并添加一个类,继承接口IMethodMonitor,Get是你自己的函数,Ori是原函数会在运行时动态生成,在Get中你可以干你想干的任何事情

    C# code?

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    public class CustomMonitor : IMethodMonitor //自定义一个类并继承IMethodMonitor接口

    {

        [Monitor("TargetNamespace""TargetClass")] //你要hook的目标方法的名称空间,类名

        public string Get() //方法签名要与目标方法一致

        {

            return "B" + Ori();

        }

     

        [MethodImpl(MethodImplOptions.NoInlining)]

        [Original] //原函数标记

        public string Ori() //方法签名要与目标方法一致

        {

            return null//这里写什么无所谓,能编译过即可

        }

    }


    然后定义目标函数,例如

    C# code?

    1

    2

    3

    4

    public string Get()

     {

        return "A";

     }


    最后调用Monitor.Install()安装监视器,例如:

    C# code?

    1

    2

    3

    Console.WrtieLine(Get());

    Monitor.Install()

    Console.WrtieLine(Get());


    你会发现第一次调用Get输出的值是"A",第二次是"BA"

    当然这个库只是hook,但hook一般都需要dll注入来配合,因为hook自身进程没什么意义,hook别人的进程才有意义,我之后会发布一个用于.net程序远程注入的类库,注入的是.net的dll哦,不是C++的

    好了,讲了这么多,其实这个库代码量并不大,但主要是自己研究的一个成果,很多东西都是自己琢磨出来的,所以觉得这个过程很有意思,也希望高手能指出改进方案,毕竟感觉目前这种方法虽然实现了功能,但是并不是很好,总觉得以hook .net虚拟机的方式来实现会更简单一些,或者网络上已经有了现成的解决方案我没有找到,总之,抛砖引玉,希望大家能共同探讨

    展开全文
  • 用React hook写一个倒计时

    千次阅读 2020-06-16 14:17:15
    import React, { useState, useEffect, useCallback, useRef } from 'react'; const CountDown: React.FC = () => { const intervalRef = useRef<any>(null); const [count, changeCount] = useState...
  • Hook内存读写

    2014-01-27 21:07:32
    Hook内存读写不解释.。 更好的操作。
  • 易语言 拦截文件读写 APIhook 拦截打开文件 拦截文件读入 拦截取文件长度 拦截文件读写位置
  • 老外的Socket hook代码

    热门讨论 2009-07-07 13:56:26
    老外的Socket hook代码,有了这你可以出自己的socket调试工具。
  • VC++HOOK

    2008-11-30 16:20:43
    编写了一个HOOK屏蔽键盘和鼠标消息 具体看说明
  • 发布一个自己的用于Hook .Net方法的类库,代码量不大,完全的C#代码实现,是一个比较有趣的功能,分享出来希望能和大家共同探讨 安装:Install-Package DotNetDetour 源码:[url=...
  • 网上很多例子讲的是hook底层的非托管的dll,主要是user32....我想直接hook一个C#的exe程序中的函数?有没有可能实现?看了那么多例子感觉也没有这方面的东西,难道是不能?或者有人实现的,提供下思路?或例子教程?
  • 前言 React hook已经出来已经很长时间,之前一直没有去了解,最近花时间看一下,还是颇有感触,使用React开发也已经有挺长时间,在开发过程中,对组件的拆分,自认做得还是比较细致,小到一个标题都会拆分成一个组件...
  • 老外的sockethook代码 实现socket钩子,提供hook代码和测试例程序-Written by foreigners sockethook hook socket code to provide hook procedure code and test cases
  • 用于Hook.Net方法的类库,代码量不大,完全的C#代码实现,是一个比较有趣的功能,分享出来希望能和大家共同探讨 1.为何想做这个 说到hook大家都应该不陌生,就是改变函数的执行流程,让本应该执行的函数跑到另一个...
  • Hook

    2020-12-21 18:32:15
    hook 规则: hook 不能在 class 使用 hook 只能在函数组件 hook 必须在最顶层 不能在 for if 这里 自定义 hook a. use 开头 b. 纯函数组件 HOC : 高阶组件是参数为组件,返回值为新组件的函数
  • hook api的一个自己的例子

    千次阅读 2004-11-05 15:17:00
    帮同学了个hook api的例子...一个简单的console工程,vc6,vc7在win2k pro,server上调试通过.#include #include #include #pragma comment(lib,"Dbghelp.lib")#pragma comment(lib,"User32.lib")typedef int (__stdcall
  • 场景:获取验证码按钮,点击获取后倒计时60s不能操作。 import React, { useState, useEffect } from 'react'; import { Button } from 'antd'; let timeChange;... const [time, setTime] = useState(60);... co...
  • 国外大牛HOOK

    2012-11-01 16:27:52
    国外大牛HOOK技术的程序,可以完成HOOK功能
  • VC 的APIHook实例源代码,VC 的APIHook实例源代码,大致翻了一下,只挂引入表的函数,注入有SetWindowHookEx和CreateRemoteThread两种方式,进程枚举也区分了不同系统下使用的psai和toolhelp,另外为了获得...
  • delphihook程序

    2008-08-26 10:49:50
    delphi hook 截获 键盘 鼠标点击事件消息 封装在一个dll中
  • OD手写HOOK

    千次阅读 2014-01-04 23:41:51
    1、程序源代码 #include "CC_Debug.h" #include int main() { int a = 3; int b = 2; int c = a + b; char tmp[8]; memset(tmp,0,8);... MessageBoxA(NULL,tmp,"HOOK",MB_OK); return 0;
  • hook d3d

    2014-08-26 21:01:12
    hook d3d
  • 、为什么要自定义Hook 将组件逻辑提到可重用的函数中,然后这些函数供它的Hook使用,或者函数组件使用,最终目的是形成公用的Hook库。 最终根据不同的场景开发自定义不同类型的Hook. 二、函数名必须为"use...
  • VB全局Hook (区分大小) ,从网上搜集的一个代码,可以顺利编译,觉得挺不错,分享给大家。这个HOOK可以监听键盘上任意一个键的状态,而且可以获得当前的大小状态,键盘符号什么的都可以取得,截图如上所示。
  • HOOK

    千次阅读 2016-02-17 10:14:30
    HOOK种通过更改程序的数据结构或代码结构从而改变程序运行路线的种方法。其中包括用户模式的HOOK和内核模式的HOOK(Windows 64bit 系统有SG保护,有些HOOK不能这样简单的实现,此处仅仅记录方法)。 用户层...
  • 易语言使用APIhook进行拦截文件读写,拦截文件读写,使用apihook
  • HOOK游戏,挂专用,上电垃圾,赚点分
  • //返回给dispacth的是一个对象 store.dispatch(actionOfAdd()) } function handleAsyncDelete() { //返回给dispacth的是一个对象 store.dispatch(actionAsyncOfDelete()) } return 点击增加 点击异步减少...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 65,735
精华内容 26,294
关键字:

怎样写一个hook