前几天逛看雪的时候,发现fickle大佬的这个思路很是不错,正好我也挺感兴趣的,就去学了一下inlinehook,嗯,不太难,所以就有了这篇文章,我从0开始讲这个代码框架,我很尽量的把这篇博客写的详细了,应该会一点点C基础就能看懂,想看原文这里https://bbs.pediy.com/thread-258054.htm

dll注入技术

就是把我们的代码加在目标进程然后执行,可以理解为一个变种的exe文件,只不过main函数变成了DLLMain,

DLLMain

MSDN定义DLLMain原型

BOOL APIENTRY DllMain( HMODULE hModule, //指向自身的句柄DWORD  ul_reason_for_call, //调用原因LPVOID lpReserved//隐式加载和显式加载//LPVOID是一个没有类型的指针,也就是说你可以将任意类型的指针赋值给LPVOID类型的变量(一般作为参数传递),然后在使用的时候在转换回来)

三个参数都是_In_类型
第二个参数

系统是在什么时候调用DllMain函数的呢?静态链接时,或动态链接时调用LoadLibrary和FreeLibrary都会调用DllMain函数。DllMain的第二个参数fdwReason指明了系统调用Dll的原因,它可能是::
DLL_PROCESS_ATTACH、
DLL_PROCESS_DETACH、
DLL_THREAD_ATTACH、
DLL_THREAD_DETACH。
以下从这四种情况来分析系统何时调用了DllMain。
进程映射
DLL_PROCESS_ATTACH
大家都知道,一个程序要调用Dll里的函数,首先要先把DLL文件映射到进程的地址空间。要把一个DLL文件映射到进程的地址空间,有两种方法:静态链接和动态链接的LoadLibrary或者LoadLibraryEx。
当一个DLL文件被映射到进程的地址空间时,系统调用该DLL的DllMain函数,传递的fdwReason参数为DLL_PROCESS_ATTACH,这种调用只会发生在第一次映射时。如果同一个进程后来为已经映射进来的DLL再次调用LoadLibrary或者LoadLibraryEx,操作系统只会增加DLL的使用次数,它不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。不同进程用LoadLibrary同一个DLL时,每个进程的第一次映射都会用DLL_PROCESS_ATTACH调用DLL的DllMain函数。
可参考DllMainTest的DLL_PROCESS_ATTACH_Test函数。
进程卸载
DLL_PROCESS_DETACH
当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的fdwReason值是DLL_PROCESS_DETACH。当DLL处理该值时,它应该执行进程相关的清理工作。
那么什么时候DLL被从进程的地址空间解除映射呢?两种情况:
FreeLibrary解除DLL映射(有几个LoadLibrary,就要有几个FreeLibrary)
进程结束而解除DLL映射,在进程结束前还没有解除DLL的映射,进程结束后会解除DLL映射。(如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。)
注意:当用DLL_PROCESS_ATTACH调用DLL的DllMain函数时,如果返回FALSE,说明没有初始化成功,系统仍会用DLL_PROCESS_DETACH调用DLL的DllMain函数。因此,必须确保清理那些没有成功初始化的东西。
可参考DllMainTest的DLL_PROCESS_DETACH_Test函数。
线程映射
DLL_THREAD_ATTACH
当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。
新创建的线程负责执行这次的DLL的DllMain函数,只有当所有的DLL都处理完这一通知后,系统才允许进程开始执行它的线程函数。
注意跟DLL_PROCESS_ATTACH的区别,我们在前面说过,第n(n>=2)次以后地把DLL映像文件映射到进程的地址空间时,是不再用DLL_PROCESS_ATTACH调用DllMain的。而DLL_THREAD_ATTACH不同,进程中的每次建立线程,都会用值DLL_THREAD_ATTACH调用DllMain函数,哪怕是线程中建立线程也一样。
线程卸载
DLL_THREAD_DETACH
如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有DLL文件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。
注意:如果线程的结束是因为系统中的一个线程调用了TerminateThread,系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数。

简单说

ul_reason_for_call的值 代表的状态
DLL_PROCESS_ATTACH  Dll刚刚映射到进程空间中
DLL_THREAD_ATTACH   进程中有新线程创建
DLL_THREAD_DETACH   进程中有新线程销毁
DLL_PROCESS_DETACH    Dll从进程空间中接触映射

一般只用DLL_PROCESS_ATTACH(Dll刚刚映射到进程空间的时候就执行相关的代码)就够了,
DLLMain函数怎么写

BOOL APIENTRY DllMain( HMODULE hModule, DWORD dwReason,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:***添加想执行的语句(功能函数)***case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH:break;}return TRUE;
}

当dll被加载(DLL_PROCESS_ATTACH)时,为了实现功能,因为这次插件要加载其他进程,所以要调用创建进程(CreateThread)调用函数(ThreadProc)
CreateThread函数

MSDN
该函数在主线程的基础上创建一个新线程。线程终止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象。
原型:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
SIZE_T dwStackSize,//initialstacksize
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
LPVOID lpParameter,//threadargument
DWORD dwCreationFlags,//creationoption
LPDWORD lpThreadId//threadidentifier
)

参数

IpThreadAttributes:指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,NULL使用默认安全性,不可以被子线程继承,否则需要定义一个结构体将它的bInheritHandle成员初始化为TRUE
第一个参数NULL就ok了
wStackSize,设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。任何情况下,Windows根据需要动态延长堆栈的大小。
第二个0就行
lpStartAddress,指向线程函数的指针,形式:@函数名,函数名称没有限制,
就是功能函数
lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
dwCreationFlags :线程标志,可取值如下
0就行
lpThreadId:保存新线程的id。```
NULL

本次形式:CreateThread(NULL, 0, LPTHREAD_START_ROUTINE(DlgThread), hModule, 0, NULL)
LPTHREAD_START_ROUTINE函数指针

插件思路

如果监控到有被撤回的消息,找到这条消息的发出者(wxid)和内容,然后弹出一个对话框(DialogBox)
先了解一下对话框

DialogBox

对话框
msdn

int DialogBox(
HINSTANCE hInstance,//对话框属于当前进程,HINSTANCE是窗口进程句柄
LPCTSTR lpTemplate,//对话框使用哪个对话框资源
HWND hWndParent,//对话框的父窗口是哪个,NULL表示没有父窗口
DLGPROC lpDialogFunc//窗口有对应的消息处理函数
);

DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, Dlgproc);
最后一个函数是回调函数,继续查MSDN

DLGPROC Dlgproc; INT_PTR Dlgproc(HWND unnamedParam1,UINT unnamedParam2,WPARAM unnamedParam3,LPARAM unnamedParam4
)
{...}

细看一下

unnamedParam1Type: HWNDA handle to the dialog box.unnamedParam2Type: UINTThe message.unnamedParam3Type: WPARAMAdditional message-specific information.unnamedParam4Type: LPARAMAdditional message-specific information.Type: INT_PTR


看这里,第二个参数

unnamedParam2Type: UINTThe message.

有好几种类型,点进去看一下WM_INITDIALOG的documentation

我找到了中文文档,浏览器翻译不太行,一般都是自己翻译。这次竟然有官方中文文档https://docs.microsoft.com/zh-cn/windows/win32/dlgbox/wm-ctlcolordlg
WM _ INITDIALOG 消息:
在对话框显示之前立即发送到对话框过程。 对话框过程通常使用此消息来初始化控件并执行任何其他影响对话框外观的初始化任务。就是表明对话框及其所有子控件都创建完毕了
所以说对话框DialogBox最后一个参数的回调函数应该这样写

INT_PTR CALLBACK Dlgproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {switch (uMsg) {case WM_INITDIALOG: {m_dialog_hwnd = hWnd;wechatWinAddr = GetWxModuleAddress();revockCallVA = wechatWinAddr + REVOCK_CALL_RVA;revockCallTargetVA = wechatWinAddr + REVOCK_CALL_TARGET_RVA;revockCallJmpBackVA = revockCallVA + 5;StartHook5(wechatWinAddr+REVOCK_CALL_RVA,backCode,_OnRevock);}break;case WM_COMMAND:break;case WM_CLOSE:close(hWnd);break;}return 0;
}

RVA是什么呢,看我的这篇笔记


call的VA = LoadLibraryW(L"WeChatWin.dll") + RVA(0x28c33f)
目标地址(撤回的消息的地址)= LoadLibraryW(L"WeChatWin.dll") + RVA(0x28ccd0)

后面就是inline hook函数了

inline hook

盗个图来解释一下流程

但是不能直接把汇编代码插入的,因为内存装载到内存里面以后,如果我们手动强制添加一串代码(指令)进去,那需要给程序重新做地址重定位,这是不可能的事情,所以我们只能在要hook的地方写入一个jmp,跳转到我们写的函数(call)执行功能,然后再跳到原来的函数去,这样内存结构不变,就是个指令替换,如上图,如果还没有看懂,我找到了另外一个图

自己也画了个图,按OD的感觉画的

inlinehook步骤

1.寻找call

构造一个长度为5字节的jmp指令(jmp的机器码占用1字节,跳转到的地址偏移占用4字节)来替换原来的5字节的call指令。E8,即我们需要寻找长度为5字节的call,来进行替换

2.jmp到指定地址

这次用到的短跳转(近距离地址),E9,如果需要hook的假设需要hook的call的指令的内存地址为:0x1000,我们想要它执行后跳转到我们的函数(假设函数在内存中的地址:0x5000),那么,构造jmp指令时,指令应为:

jmp (0x5000-(0x1000 + 5))
jmp 0x3FFB

因为jmp指令占5字节,所以要加5

hook代码框架


Write(Read)ProcessMemory太简单了,但是正好有好久好久之前的笔记,那就放出来吧,省得你们去查MSDN了
okk,对应Write(Read)ProcessMemory的文档就很明确了,hookAddr是要被替换的call指令的内存地址(0x1000);backCode是长度为5字节的数组缓冲区,写的是call指令(E9 0x3FF8);FuncBeCall是0x5000,用OpenProcess打开当前进程,然后读取当前进程hookAddr处的指令,写入backCode数组,再将构造好的jmp指令写入当前进程hookAddr处。
StartHook函数第3个参数接收一个函数地址,这个函数地址指向的函数应该是这样的:

DWORD tEsp = 0;
_declspec(naked) void _OnRevock() {__asm {mov tEsp, esppushad}OnRevock(tEsp);__asm {popadcall revockCallTargetVAjmp revockCallJmpBackVA}
}

OnRevock(tEsp)就是功能函数

void OnRevock(DWORD esp) {wchar_t *tips = *(wchar_t **)(esp + 0xC);wchar_t *msg = *(wchar_t **)(esp + 0x10);if (NULL != tips) {WCHAR buffer[8192];wchar_t* pos = wcsstr(tips, L"撤回了一条消息");if (pos!= NULL && NULL != msg) {swprintf_s(buffer, L"* %s,内容:%s \r\n\r\n",tips, msg);HWND hTextBox = GetDlgItem(m_dialog_hwnd, IDC_EDIT1);//EM_SETSEL消息,wParam代表开始,lParam代表结束SendMessage(hTextBox, EM_SETSEL, -1, -1);//EM_REPLACESEL消息,wParam代表是否可以撤销替换,lParam代表替换成的文本SendMessage(hTextBox, EM_REPLACESEL, 0, (LPARAM)buffer);}}}

裸函数

_OnRevock函数用_declspec(naked)修饰,被它修饰的函数我们常称它为裸函数,编译器不对裸函数做任何操作,也就是说裸函数就是我们自己写的汇编代码,裸函数的特点是在编译生成的时候不会产生过多用于平衡堆栈的指令,这意味着在裸函数中我们要编写内联汇编控制堆栈平衡。一个比较简单写法是备份所有的寄存器,做完其他操作后再把寄存器的值还原回去,

笨代码示例

DWORD tEax = 0,tEcx = 0,tEdx = 0,tEbx = 0,tEsp = 0,tEbp = 0,tEsi = 0,tEdi = 0;
_declspec(naked) void OnCall() {__asm {mov tEax, eaxmov tEcx, ecxmov tEdx, edxmov tEbx, ebxmov tEsp, espmov tEbp, ebpmov tEsi, esimov tEdi, edi}//do something__asm {mov eax, tEaxmov ecx, tEcxmov edx, tEdxmov ebx, tEbxmov esp, tEspmov ebp, tEbpmov esi, tEsimov edi, tEdicall ...jmp ...}
}

裸函数规则

The following rules and limitations apply to naked functions(摘自MSDN)

官方示例代码

// nkdfastcl.cpp
// compile with: /c
// processor: x86
__declspec(naked) int __fastcall  power(int i, int j) {// calculates i^j, assumes that j >= 0// prolog__asm {push ebpmov ebp, espsub esp, __LOCAL_SIZE// store ECX and EDX into stack locations allocated for i and jmov i, ecxmov j, edx}{int k = 1;   // return valuewhile (j-- > 0)k *= i;__asm {mov eax, k};}// epilog__asm {mov esp, ebppop ebpret}
}

改良裸函数
因为tEax = 0,tEcx = 0,tEdx = 0,tEbx = 0,tEsp = 0,tEbp = 0,tEsi = 0,tEdi = 0;
寄存器入栈可以改变一下

改前
push EAX
push ECX
push EDX
push EBX
push ESP
push EBP
push ESI
push EDI
改后
pushad

同理,mov 8个通用寄存器, tEax改为popad
所以,改后就成为了下面这种模板

_declspec(naked) void OnCall() {__asm {pushad}//do something 功能函数__asm {popadcall ...jmp ...}
}

卸载inline hook

卸载hook的流程比较简单,也是打开当前进程,把hook时备份的call指令写回原来的位置

int Unhook(DWORD hookAddr, BYTE backCode[5]) {HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, GetCurrentProcessId());if (WriteProcessMemory(hProcess, (LPVOID)hookAddr, backCode, 5, NULL) == 0) {return -1;}return 0;
}

参数hookAddr是原来的call的内存地址,backCode是之前备份下来的call指令(EB XXXX0000)

将dll注入程序

怎么说,正好手头有例子,看下恶意代码例子12-01吧,xp虚拟机启动,IDA启动


每分钟都会有个讨厌的弹窗,而且我虚拟机里的这个IDA没用反汇编插件,反正汇编也不难,直接分析吧

LoadLibraryA和GetProcAddress将psapi.dll中的三个函数的地址分别保存在dword_408714,dword_40870c,dword_408710
我继续向下看,到这我发现有点不太对
很明显了,用lab12-01.dll来调用LoadLibraryA然后注入Lab12-01.dll,和我们这次要说的没啥关系
浪费我五分钟时间,不说这个了,感兴趣的看这个博客https://blog.csdn.net/wangtiankuo/article/details/77097536,短小精干,我下次遇到inlinehook恶意代码我再补充

注入器来着吾爱大神,帖子https://www.52pojie.cn/thread-429548-1-1.html
注入器放网盘了
链接:https://pan.baidu.com/s/1UkIb-ODMQgzjNTBdxNjixQ
提取码:hlll
说实话,这个老哥把注入器的源码发出来了,真想今天晚上就搞明白,给这篇文章再加一个从0带你写注入器,MD,两点了熬不动了,明天周五和周末搞
源码放网盘了,带了点配置文件,懒
链接:https://pan.baidu.com/s/19Yn1L7guj-p5NjPBGpe2ag
提取码:hlll

从0带你写插件之微信防撤回,保姆级教学代码一行一行解读相关推荐

  1. PC端微信防撤回,多开插件

    插件下载 微信防撤回多开插件 提取码:vp6x 插件安装 下载后解压到微信安装目录,可以右键微信图标,选择打开文件所在的位置,将解压后的插件放到该目录下.然后将微信退出,双击打开插件,点击应用即可.

  2. 微信防撤回python代码_python实现微信防撤回神器

    本文实例为大家分享了python实现微信防撤回神器的具体代码,供大家参考,具体内容如下 手写辛苦,希望给赞 #!/usr/local/bin/python3 # coding=utf-8 import ...

  3. 用Python写微信防撤回脚本,锁定那些被撤回的消息,就是撤回了也可以看到

    如果好友短时间发送多条消息然后撤回会难以判断究竟撤回的是哪条信息,只能靠猜.后来我觉得"猜"这个事情特别不Pythonic,研究一段时间后找到了解决方案,不得不惊叹ItChat真的 ...

  4. python接收微信消息_【Python写微信防撤回脚本】02 接收记录聊天信息

    上一期我们安装好了ItChat,并且学会用它登录微信.想知道它还能做什么?往下滑吧~ 接收好友信息 利用ItChat登录微信之后,我们就可以自动记录好友发来的信息. 话不多说,直接上代码: impor ...

  5. GitHub上这个微信防撤回的开源项目,99%的程序员不知道

    我知道你知道很多种微信消息的撤回方式,如果你一种都不知道,那么请你往下看. 关于微信发错消息的尴尬,估计大家都有不堪回首的历史. 开个玩笑 假如你是这个老板? 画面引起极度不适,[手动捂脸] 你想想, ...

  6. 微信多开工具,微信防撤回、QQ防撤回、Tim防撤回工具,微信消息防撤回、QQ消息防撤回、Tim消息防撤回,无视撤回功能,不错过每一条消息

    这是一款非常小巧精致的防撤回和微信多开工具,只有130kb的大小,但功能却非常强大. 一.软件简介 这是一个开源项目,程序源码全部开源,因此这款工具具有极高的安全性,毕竟代码就公开在大家的眼皮底下,谁 ...

  7. python 基于itchat详解微信防撤回程序

    itchat学习笔记请见另一文章 文章目录 0. 选题背景 1. 发现问题 2. 提炼问题 3. 解决方案 4. 分析设计 4.0 准备 问题4.1 : 如何用程序登录自己的微信账号? 问题4.2 : ...

  8. 信息时代——微信防撤回(Python实现)

    目录 1 前言 2 有微信联想起的哲思 2.1 哲学思维开始冒头 2.2 哲学期 2.3 科学时代 2.4 后科学时代 3 微信防撤回完整代码奉上 1 前言 她总是微信撤回,我得想一个法子治治她,哈哈 ...

  9. python微信库wxpy_python wxpy微信防撤回功能

    python使用wxpy轻松实现微信防撤回的方法 最近比较闲就随便瞎看,看到了微信防撤回就顺便跟着学着实现一下 使用的是wxpy,安装方法pip install wxpy(我使用的是python2.7 ...

最新文章

  1. Qt5下OpenGL程序的新写法
  2. docker安装redis提示没有日记写入权限_浅析Linux下Redis的攻击面(一)
  3. git commit 提交的时候报错husky > pre-commit hook failed 或者‘lint-staged‘ 不是内部或外部命令,也不是可运行的程序(解决办法)
  4. 后台开发人员面试内容——数据库(二)
  5. Boost.MultiIndex 使用序列索引的示例
  6. 2022 年是 Linux 桌面年吗?
  7. 若非必要请勿直接使用@_
  8. 浅谈网站遇到问题时的解决办法及提问技巧
  9. 继涉黄被约谈 “比心陪练”App因内容涉宣扬暴力再被处罚
  10. linux安装mysql5.6rpm_centos6.5 下安装mysql5.6,rpm方式
  11. 编程之美读书笔记2.15 - 子数组之和的最大值(二维)
  12. IntelliJ Idea14 创建Maven多模块项目,多继承,热部署配置总结(一)
  13. Linux(centos7下载安装)
  14. JAVA毕业设计共享汽车管理系统计算机源码+lw文档+系统+调试部署+数据库
  15. 植被农业数据下载网站整理
  16. 机器学习入门 笔记(二) 机器学习基础概念
  17. layert弹出层关于layer.open,打开页面时的用法
  18. IDT7206简明资料
  19. javascript错误:对象不支持此属性或方法
  20. Docker 搭建FastDFS文件系统

热门文章

  1. 达人评测锐龙r7 5800h和酷睿i7 12650h选哪个好
  2. DRG/DIP分组器HIS、电子病历、病案等系统调用接口说明,支持java c#等多种语言,有c#代码参考
  3. 【实时渲染】屏幕空间特效和多边形技术
  4. 互联网黑市分析:社工库的传说
  5. Pytorch报错:“freeze_support()“ line can be omitted if the program is not going to be frozen(已解决)
  6. 双十一 唯品会内部优惠入口
  7. 已然神话的区块链的应用了解一下-MGCEX
  8. Unity3D教程:简单触发器实现自动开关门
  9. Win10开始菜单打不开怎么办?
  10. 百度富文本编辑器  ueditor 的基本使用