记一次挂钩经历

工作中遇到个问题:需要在没有代码的情况下禁用某软件的一个功能。该软件为Windows软件。

接到任务后,先理下思路:

  1. 没有源码的情况下肯定需要反汇编,那少不了要用到神器IDA。
  2. 定位具体函数。如果目标模块有符号的话,则会大大提升定位的效率,如果没有则只能一点点分析,缩小范围了。
  3. 定位了具体函数后,就需要在目标进程中找到该函数并将其替换成自己的函数。
    这将会用到注入和挂钩技术,现在已经有很成熟的Detour了,所以这步应该不难。

这几个过程里最耗时的是步骤2,如果目标模块没有符号,那任务将更加艰巨。

好了,开始干活。

1. 由于要禁用的功能是从界面事件激发的,所以考虑先从界面模块入手。但是经过分析发现目标软件的界面库是一套自绘界面库,并没用到任何Windows系统控件,所以放弃,转为从功能模块的dll入手。

2. 用IDA打开目标模块,如果目标模块包含了符号,那么在 Functions Window中看到的是真实的函数名,如图:

并且在IDA View窗口,会看到很多友好的注释和函数名,帮助你分析汇编代码,如图:

很悲催,我看到的是这样的:

表明目标模块中没有符号。

3. 无奈只能想办法缩小范围了。通过以下几种方法分析软件的行为

  • 使用ProcessMonitor分析软件的行为。这个软件可以监测软件读写注册表、文件、网络等信息。是微软官方的工具。

  • 使用ProcessExplorer动态分析线程和堆栈信息,这也是微软官方的工具。如果直接用IDA调试的话,可以不用这个。

  • 使用IDA不断调试来验证自己的想法。

经过长时间的分析,最终发现在该软件设置界面启用目标功能时,该软件会向系统的AppData目录下写一个配置文件。在IDA里搜索该配置文件的文件名,找到了几处可疑的地方。这下就简单了,在可疑的地方打上断点,用IDA调试。最终确定的函数如下:

push    ebp
mov     ebp, esp
mov     ecx, [ecx+7Ch]
push    [ebp+8]
add     ecx, 0B10h
push    ecx
mov     eax, [ecx]
mov     edx, [eax+24h]
mov     eax, esp
push    1
mov     dword ptr [eax], 7A98DDFCh
call    edx
pop     ebp
retn    4

该函数的字节码如下:

55 8B EC 8B 49 7C FF 75 08 81 C1 10 0B 00 00 51 8B 01 8B 50 24 8B C4 6A 01 C7 00 FC DD 98 7A FF D2 5D C2 04

4. 接下来,在自己的模块中实现搜索字节码的功能。一个开源库的简单实现:

/* Scan for the signature in memory then return the starting position's address */
void* CSigScan::FindSignature(void) noexcept {
    const unsigned char *pBasePtr = base_addr;
    const unsigned char *pEndPtr = base_addr+base_len;
    size_t i = 0;
 
    while(pBasePtr < pEndPtr) {
        for(i = 0;i < sig_len;i++) {
            if((sig_mask[i] != '?') && (sig_str[i] != pBasePtr[i]))
                break;
        }
 
        // If 'i' reached the end, we know we have a match!
        if(i == sig_len)
            return (void*)pBasePtr;
 
        pBasePtr++;
    }
 
    return nullptr;
}

其中的sig_str就是字节码,sig_mask为占位符,类似这样 "xxxxxxxxxx???xxxxx", 其中 x 为要搜索的字节,? 为忽略字节。
本以为会很顺利,结果打脸,没搜到。反复检查发现,由于目标进程加载了我的注入模块,改变了后续模块的加载地址,这导致模块中一些常量字串的地址发生了偏移,所以我们需要忽略这函数中引用的常量地址。就是这句

mov     dword ptr [eax], 7A98DDFCh

这句中的 7A98DDFCh 是常量地址,对应字节码 倒数第9 ~ 倒数第6 字节。

FC DD 98 7A

我们在搜索时忽略这4个字节就成了。

5. 讲下注入。注入的方法有很多。比如:CreateRemoteThreadEx,SetWindowsHookEx 等。而我采用了 DetourCreateProcessWithDllExW 方案。这条函数虽然会启动目标进程,但这样做有个好处,可以在所有其它模块执行前加载你的模块。

6. 替换目标函数。使用Detour很简单就实现了。

DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(m_fnOld, m_fnReplacement);
DetourTransactionCommit();

在替换时,如果目标函数是一个类的成员函数,还需要些小技巧。类成员的调用方式为 __thiscall。而vc不允许我们定义__thiscall,否则编译会报错:

error C3865: '__thiscall' : can only be used on native member functions

我们可以定义一个 __fastcall 调用方式的函数 来替换 __thiscall 。由于__thiscall 默认用 ECX传递this指针作为第一个参数,而__fastcall默认用 ECX, EDX传递前两个参数,所以我们在定义时要多定义一个没用的参数,像下面这样:

typedef int (__thiscall *myFooPtr)( void* , unsigned int, unsigned int); 
char* __fastcall myFoo( void* This, void* notUsed , unsigned int firstParam, 
unsigned int secondParam) { }

可以看到我们定义的 __fastcall 函数,比声明的原函数类型多一个 notUsed参数。我的函数的实现很简单,将传入的int类型参数直接改为 0 并传入原始函数就成了。

终于实现了既定目标,看了看键盘上掉落的头发,我心里些许有些安慰 。。。😂

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值