吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4730|回复: 18
收起左侧

[其他转载] 8句代码实现win10下Delphi 10.3 inline hook之MessageBoxA

[复制链接]
bester 发表于 2019-7-8 11:18
本帖最后由 bester 于 2019-7-8 11:33 编辑

首先感谢Delphi学习交流群的myy,昨天调试了6个小时,终于完成了,记录一下,以免有爱好者走弯路,某度的资料我等菜鸟看不懂,思来想去还是自己挖坑自己填。
win10下注意两个问题:

1.OpenProcess无法获取进程句柄,一般此问题在Dll中调用此函数出现,用GetLastError返回5,意思是拒绝访问,DLL暂时没试验以下三种办法
  解决方法:
    1.EXE用管理员运行再用OD附加
    2.用提权函数提升至DEBUG权限
    3.DELPHI自身IDE运行EXE不会出现此问题

2.WriteProcessMemory的第5个参数,IpNumberOfByteWritten(表示:实际写入字节),他的类型不是Cardinal类型,是NativeUInt类型,这里参考写错了,如果写Cardinal类型会提示实参和形参不一致。也可能是NativeUInt类型是Cardinal类型的分支,这里不做探究,反正Delphi的参考进去了winapi.windows看了源码以后,他的参考又不一样了,坑。
1.png

代码如下,会逐句解释
[Delphi] 纯文本查看 复制代码
function lens(x,y:Integer):Integer;               //声明一个函数lens,lens函数是计算JMP距离的,也就是从api函数跳转到新函数的距离
begin
  Result:=x-y-5;                  // X=新函数地址,Y=api函数地址,由于Jmp(16进制E9占1个字节)+距离(占4个字节),实际上跳转是从api地址+5开始起跳,如果不减5,实际上会跳到新函数的地址+5
end;                                //例:如果新函数地址=00000006,api地址=00000000,那么起跳位置实际是00000006-00000000-5,如果不减5,将会跳转到00000006+5的位置

function MyMessages(hWnd: Cardinal; lpText, lpCaption: PAnsiChar; uType: Cardinal):Integer; stdcall;   //声明一个和MessageBoxA函数一样类型的函数,命名为MyMessages,注意一定要用stdcall约定,否则会调用出错
var
  text: PAnsiChar;               //定义一个变量为text,类型和形式参数的类型一样为PAnsiChar,便于操作形式参数
begin                              //注意声明一个函数,不论函数是否有代码,都应该用begin end括起来,否则IDE无限提示出错
  text := lpText;                 //令ipText的值=text,这样的话,我就能获取到MessageBoxA的参数的内容了
  ShowMessage('我获取到的信息框标题是: ' + text);    //此时调用ShowMessage,调试输出MessageBoxA的IpText参数的值

end;

procedure TForm1.Button2Click(Sender: TObject);      //这里是一个按钮的按钮事件,我添加了两个按钮,一个是调用原始信息框的按钮,一个是Hook按钮,这里是Hook按钮
  var
    hp: Integer;                                                                 //定义一个变量为HP,用来保存进程句柄
    apiaddr: Integer;                                                        //定义一个变量为apiaddr,用来保存获取到的api地址,获取到的api的地址为10进制,但我们无需转成OD的16进制,为了方便查看可以手动调用calc计算器进行转换或者ctrl+F5添加监视
    read: array[1..5] of Byte;                                            //定义一个元素序号为1-5的数组,数组名为read,类型为byte类型,表示保存5个字节,用来保存api函数的5个字节,因为我们需要修改这5个字节跳转到新函数,到时候可便于恢复Hook
    rd: NativeUInt;                                                          //定义一个变量为rd,类型为NativeUInt,这个变量是函数保存实际操作了几个字节,比如我读取或者写入了5个字节,如果操作成功,这里的值就是5,否则就是0,上面有作说明,类型必须为NativeUInt,
    jmps: Integer;                                                           //定义一个变量为Jmps,这里用来保存Jmp的机器码(E9)的十进制,因为我们操作的是10进制,上面说了无需转换成OD的16进制
    tiaoshi,tiaoshi1: Integer;                                          //这里用来调试输出内容,方便我查找问题,可以不要。
  begin
    hp := OpenProcess(PROCESS_ALL_ACCESS, False, GetCurrentProcessId);              //OpenProcess函数,如果操作成功,那么hp变量将保存进程句柄,第三个参数用GetCurrentProcessId,表示获取自身进程的进程ID,如果是操作别的进程,这里要填需要操作的进程的进程ID,但注意要用DLL形式,EXE无法操作
    apiaddr := Integer(GetProcAddress(LoadLibrary('user32.dll'), 'MessageBoxA'));   //这里用LoadLibrary获取user32.dll的模块地址,也可以说是模块句柄,再用GetProcessAddress获取MessageBoxA的api地址,GetProcessAddress返回的是Pointer类型,所以用Integer转成整数型,也就是10进制
    ReadProcessMemory(hp, Pointer(apiaddr), Pointer(@read), 5, rd);       //用ReadProcessMemory读取api函数的头5个字节,保存在read数组当中,ReadProcessMemory(进程句柄:Cradinal类型,读取地址:pointer类型,保存读取的数据:pointer类型(加@表示保存的数据的地址,也就是所谓的传址),读取数据的大小:Cradinal类型,实际大小:NativeUInt类型)
    jmps := 233;                                                                                                          //JMP的机器码(E9)的十进制
    WriteProcessMemory(hp, Pointer(apiaddr), Pointer(@jmps), 1, rd);                    //将E9写入到首地址的第一个字节
    tiaoshi:=Integer(@MyMessages) ;                                                                       //这里我调试输出查看一下是否获取到新函数的地址,加@表示取新函数的实际地址,跟上面的readprocessmemory的第三个参数是一样的意思。
    tiaoshi1 := lens(Integer(@MyMessages), apiaddr);                                             //这里我调试输出查看距离(占用4个字节),也就是JMP后面的4个字节的机器码,实际上转换成16进制是这样的形式,假设调试输出的值是01020304,那么在OD中查看需要倒转一下,也就是04030201,这样的形式
    WriteProcessMemory(hp, Pointer(apiaddr + 1), Pointer(@tiaoshi1), 4, rd);       //这里将距离写入,因为获取到的api的地址是第一个字节我们写入了E9,所以要从第二个字节开始写入4个字节的大小,所以apiaddr+1
    CloseHandle(hp) ;                                                                                              //这里关闭我们打开的进程句柄,虽然我也不知道是为什么,但是好像就是要关闭。

  end;

procedure TForm1.Button1Click(Sender: TObject);                                      //这里就是我说的第一个按钮,调用原始的MessageBoxA
begin
  MessageBoxA(0, '我是标题', '我是内容', 0)
end;



下期讲注入器部分以及怎么把上面的代码在DLL中使用,这里用EXE是为了方便我调试,有错误欢迎指正,欢迎交流Delphi,我的博客园里面会写一些文章,因为菜所以菜。

免费评分

参与人数 4吾爱币 +6 热心值 +4 收起 理由
asong + 1 + 1 谢谢@Thanks!
苏紫方璇 + 3 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
朱朱你堕落了 + 1 + 1 最好的HOOK是七字节热补丁方式,也是微软推荐的HOOK方式。
homejun + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

朱朱你堕落了 发表于 2019-7-8 18:13
本帖最后由 朱朱你堕落了 于 2019-7-8 18:42 编辑
bester 发表于 2019-7-8 12:21
@朱朱你堕落了 看了一下你说的这个,但是我还是不明白,说一下我的理解,其实不管是5字节还是7字节,都改变 ...

不好意思,哥们,有事没及时回。
一般的5字节的inline hook都是修改API的函数头,我们知道一般的API的函数头就是mov edi, mdi。
那么我写的跳到我自己DLL里面的自定义函数时,如下图

0.png

很明显,那么原始的API函数是如下的
1.png

很明显,如果API执行是从push ebp执行,会不会影响呢,当然是不影响的,这就是热补丁调用API的方法。

因为mov edi,edi,EDI自身MOV给自身,无任意意义的。
那么问题MS会这么设计呢,因为就是为了给程序打热补丁。

热补丁实现如下图
2.png

优点就是稳定,在多线程中也稳定。为什么稳定。这样说吧。

当API函数时被触发时,那么调用的是API的函数头,API的函数头这里是一个71A24A07  jmp 71A24A02
那么这个跳就跳到了上面的那个,原来5个NOP处,5个NOP已经被修改为了跳到我自定义的函数。
原来MOV EID,EID是两个字节,上面的NOP是五个字节,一共是7个字节,两个连跳就构成了7字节的热补丁HOOK

那么你一定很好奇,那怎么调用API呢,答案就是调用API函数+2的地方,这样从push ebp开始走,是不影响API的调用的。
而一般的5字节的inline hook,你可以看到,它从mov edi,edi就开始占用5个字节,push ebp等代码被覆盖,如果要调用API时,一定得先脱钩。
即先得恢复过来,才能调用API,要不,不崩溃才怪呢,脱钩后,调用API函数后,还要再重新挂钩,不重新挂钩,当下次有API调用时,还怎么
玩?

我们看到inline hook的方式就是这样

myfun()
{
//1
脱钩
//2
调用原始API
//3
再重新挂钩
}

这种很频繁的脱钩和挂钩很影响系统性能,自然也影响程序稳定性。

而7字节热补丁方式就不存在这个问题,因为一旦挂上,那么API头部就是两个连跳。。我们在自己的自定义函数中,就不需要脱钩,挂钩,
调用API时,使用的是函数头+2调用的方式。函数头+2就是push ebp这里开始,这样就根本不影响调用原API的功能。

哥们理解一下。

点评

5字节也是可以的,只不过调用原函数需要自己把hook修改的代码补上就可以了,这样就不需要频繁的恢复了。当然有热补丁位置的话还是热补丁要方便一点  详情 回复 发表于 2019-7-8 18:51

免费评分

参与人数 2吾爱币 +2 热心值 +2 收起 理由
bester + 1 + 1 我很赞同!
苏紫方璇 + 1 + 1 用心讨论,共获提升!

查看全部评分

朱朱你堕落了 发表于 2019-7-8 19:11
bester 发表于 2019-7-8 18:50
懂意思了,但是我这边有几个问题:
1.热补丁脱钩要是直接把EBF9还原为mov edi,edi吗?
2.如果我挂钩了 ...

1 脱钩当然是恢复到最原始的状态。即还原mov edi,edi,还有上面的5个NOP
2. 我不知道delphi中是如何写的,C++中是用的函数指针的方式。

举个例子
typedef int (WINAPI* PFNconnect)(SOCKET s, const struct sockaddr* name, int namelen);
int WINAPI My_connect(SOCKET s, const struct sockaddr* name, int namelen)
{
        //......其他一些代码

        
        //就是下面这样调用API的。
        int bRet;
        FARPROC pFunc;

        pFunc = GetProcAddress(GetModuleHandle("ws2_32.dll"), "connect");//获取函数地址,这时获取到的是jmp处地址即原来mov edi,edi这个地方
        pFunc = (FARPROC)((DWORD)pFunc+2); //地址+2

        bRet = ((PFNconnect)pFunc)(s, name, namelen); //函数指针方式调用

        return bRet;
}
 楼主| bester 发表于 2019-7-8 12:21
@朱朱你堕落了 看了一下你说的这个,但是我还是不明白,说一下我的理解,其实不管是5字节还是7字节,都改变了函数,7字节热补丁说是多线程下inline hook的解决办法,但是我想不明白就是
假设有两个线程同时访问MessageBoxA,5字节和7字节其实都是改变了原来的API的位置,所以这两个线程同时会执行到我自己定义的函数,如果我只想要其中一个线程执行到我的函数,好像也不可能实现吧?
我不明白的就是这句话:即使Hook失败,也不影响函数的继续执行
怎么会hook失败呢?如果hook住了,那么这两个线程同时会访问我的函数,如果没hook成功,那么相当于我这个writeprocessmemory也写入失败,如果是像文章所说,构造死循环的话,那么这两个线程也同时会暂停在这个死循环上面啊,我也不可能让这两个线程排好队一个一个访问我的函数

点评

不好意思,哥们,有事没及时回。 一般的5字节的inline hook都是修改API的函数头,我们知道一般的API的函数头就是mov edi, mdi。 那么我写的跳到我自己DLL里面的自定义函数时,如下图 [attachimg]1590183[/attac  详情 回复 发表于 2019-7-8 18:13
 楼主| bester 发表于 2019-7-8 18:50
朱朱你堕落了 发表于 2019-7-8 18:13
不好意思,哥们,有事没及时回。
一般的5字节的inline hook都是修改API的函数头,我们知道一般的API的函 ...

懂意思了,但是我这边有几个问题:
1.热补丁脱钩要是直接把EBF9还原为mov edi,edi吗?
2.如果我挂钩了,想调用API函数的话,怎么能从API地址+2处开始调用?

点评

1 脱钩当然是恢复到最原始的状态。即还原mov edi,edi,还是上面的5个NOP 2. 我不知道delphi中是如何写的,C++中是用的函数指针的方式。 举个例子 typedef int (WINAPI* PFNconnect)(SOCKET s, const struct soc  详情 回复 发表于 2019-7-8 19:11
苏紫方璇 发表于 2019-7-8 18:51
朱朱你堕落了 发表于 2019-7-8 18:13
不好意思,哥们,有事没及时回。
一般的5字节的inline hook都是修改API的函数头,我们知道一般的API的函 ...

5字节也是可以的,只不过调用原函数需要自己把hook修改的代码补上就可以了,这样就不需要频繁的恢复了。当然有热补丁位置的话还是热补丁要方便一点

点评

你说的那个把HOOK修改的代码补上来,我突然想到,好像见过代码用的是内联汇编实现为补的。如果我没有记错的话、  详情 回复 发表于 2019-7-8 19:34

免费评分

参与人数 1热心值 +1 收起 理由
朱朱你堕落了 + 1 用心讨论,共获提升!

查看全部评分

 楼主| bester 发表于 2019-7-8 19:32
朱朱你堕落了 发表于 2019-7-8 19:11
1 脱钩当然是恢复到最原始的状态。即还原mov edi,edi,还有上面的5个NOP
2. 我不知道delphi中是如何写的 ...

我试一下 看看你的这个方式能不能使用,我能不能这样理解,就是7字节比5字节稳定的好处就在于,可以不用恢复钩子,或者说只需恢复2个字节,就能调用原函数?因为除了这点,我还是没能理解到7字节比5字节更好的地方,都涉及到了内存操作,并不是7字节就不用内存操作,只是操作的字节数的多少的问题

点评

哥们,你先把inline hook彻底玩透了,加深了理解,彻底明白了,再了解热补丁,就知道了好处了。5字节HOOK是最常用的,热补丁有一个局限性就是不是所有的API函数的开头都是mov edi,eid,上面还有5个NOP的,但是5字节  详情 回复 发表于 2019-7-8 19:37
朱朱你堕落了 发表于 2019-7-8 19:34
苏紫方璇 发表于 2019-7-8 18:51
5字节也是可以的,只不过调用原函数需要自己把hook修改的代码补上就可以了,这样就不需要频繁的恢复了。 ...

你说的那个把HOOK修改的代码补上来,我突然想到,好像见过代码用的是内联汇编实现为补的。如果我没有记错的话、
朱朱你堕落了 发表于 2019-7-8 19:37
bester 发表于 2019-7-8 19:32
我试一下 看看你的这个方式能不能使用,我能不能这样理解,就是7字节比5字节稳定的好处就在于,可以不用 ...

哥们,你先把inline hook彻底玩透了,加深了理解,彻底明白了,再了解热补丁,就知道了好处了。5字节HOOK是最常用的,热补丁有一个局限性就是不是所有的API函数的开头都是mov  edi,eid,上面还有5个NOP的,但是5字节这种可以通杀所有的API,普通函数也可以HOOK,也是最常用的。慢慢来。
 楼主| bester 发表于 2019-7-8 19:39
苏紫方璇 发表于 2019-7-8 18:51
5字节也是可以的,只不过调用原函数需要自己把hook修改的代码补上就可以了,这样就不需要频繁的恢复了。 ...

咦,这怎么补?不恢复难道在新函数下面先asm把hook的那5个字节补上去?可是这样确定不会出错吗?

点评

一般不会出错,实现方法是分配一段内存,然后把hook修改的指令写进去,然后加一个跳转到hook点的下一句指令就可以了。等于把修改的部分恢复了。至于怎么写入被修改的指令,一般的可以读取内存(需要用反编译引擎确定  详情 回复 发表于 2019-7-8 20:34
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-26 02:56

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表