今天用了一天的时间在调试第二部分代码,本以为在昨天代码的基础上实现第二部分功能应该比较简单,没想到还是费了不少力气,呵呵。
好了,还是先说下大体思路,如果需要调用其它进程的内部函数做自己的事情,那么必须先把需要调用的函数的参数传递给目标函数,要实现这个功能我还是选用管道通信技术,毕竟上一部分已经建立了一个管道。然后由我们的程序把参数利用管道发给已经插入到目标进程中的DLL,DLL收到参数后,进行相应处理,组织好参数的格式,然后根据需要调用的函数要求,逐个压入堆栈,然后CALL目标函数执行。
我们先来做DLL部分,需要以下步骤。
1、在DLL初始化时,建立一个新的线程用来接收函数的参数并调用目标函数。可能大家会觉得奇怪,为什么要建立新的线程,用DLL初始化的线程不行么?我开始也不想建立新线程,可是没想到利用DLL初始化线程的时候会导致DLL初始化无法结束,因为我们的接收过程是个死循环,所以必须建立新线程,否则目标进程会卡死在我们的DLL初始化过程。
2、在线程内添加代码构建一个死循环,用来读取管道内发送过来的数据。
3、根据数据内容进行判断,是否调用目标函数。为什么会有这一步呢,因为我在调试的过程中发现读取管道的函数ReadFile,是一个同步的函数,就是说它如果读不出来数据,就会一直卡在那里,导致整个目标进程无法正常运转。所以我在管道的服务器端同样构建了一个循环,一直发送01这个字节集到管道,所以DLL的接收端就能不断从管道内读出01这个字节。所以要加入这个判断,如果读出的字节是01那么就不处理这个数据。
4、如果收到的数据的第一个字节大于01,那么即可开始组织数据,压入堆栈,调用函数。
下面看下汇编原代码:
.386
.model flat, stdcall
option casemap :none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
includelib ws2_32.lib
include ws2_32.inc
PATCH_POSITION equ 005609ebh
WSASendBuf struct ;需要调用的目标函数中的一个参数的结构
dwSize dd ?
dwAddr dd ?
WSASendBuf ends
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hModel dd ? ;本模块入口地址
dwAddress dd ? ;跳转回去的目标地址
lpBuffers dd ? ;数据包地址
dwProcAddr dd ? ;跳转函数的入口地址
hPipe dd ? ;管道句柄
dwSize dd ? ;已经发送数据的长度、字节
dwMemAdd dd ? ;发送数据的地址
dwPackSize dd ? ;需要发送数据的长度、字节
lpBufRecv dd ? ;接收管道数据包地址
szBuffer db 1024 dump (?) ;数据处理空间
hSocket dd ? ;SOCKET句柄
dwWsasend WSASendBuf <>
dwSizePipe dd ? ;模拟发送的数据长度
hRecvPipe dd ? ;用于接受管道数据的管道句柄
dwRecvThreadID dd ?
dwCode dd ?
.const
szText db '再如成功',0
szProc db '_lanjie',0
szModel db 'mylib.dll',0
szPipeName db '\\.\Pipe\masm',0
szRecvPipeName db '\\.\Pipe\Recv',0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
_Recv proc
invoke WaitNamedPipe,addr szPipeName,500h ;建立管道连接
invoke CreateFile,addr szPipeName,GENERIC_READ or GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED,NULL
mov hPipe,eax
mov eax,offset szBuffer
mov lpBufRecv,eax
;构建死循环,读取管道数据
.while TRUE
invoke ReadFile,hPipe,lpBufRecv,1024,offset dwSizePipe,NULL
mov al,byte ptr [szBuffer]
.if al>1 ;判断是否为有用的数据
mov eax,dwSizePipe ;组织函数参数
mov dwWsasend.dwSize,eax
mov eax,offset lpBufRecv
mov dwWsasend.dwAddr,eax
push 0 ;参数开始入栈
push 0
push 0
push offset dwSizePipe
push 1
push offset dwWsasend
push hSocket
mov eax,dword ptr [0057c75ch] ;0057c75c地址处的内容就是目标函数的入口地址
mov eax,[eax]
call eax ;调用目标函数
.endif
.endw
ret
_Recv endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DllEntry proc _hInstance,_dwReason,_dwReserved
mov eax,_dwReason
.if eax == DLL_PROCESS_ATTACH
invoke MessageBox,NULL,addr szText,addr szModel,MB_OKCANCEL
invoke GetModuleHandle,addr szModel ;获得插入DLL函数入口地址
mov hModel,eax
invoke GetProcAddress,hModel,addr szProc
mov dwProcAddr,eax
mov edx,005609ebh ;修改参数代码跳转到本函数
mov al,0e9h
mov byte ptr [edx],al
sub dwProcAddr,005609ebh
sub dwProcAddr,5
mov eax,dwProcAddr
mov dword ptr [edx+1],eax
mov dwAddress,005609f0h
invoke CreateThread,NULL,0,offset _Recv,NULL,NULL,addr dwRecvThreadID
;mov edx,00560a05h ;跳过错误检测
;mov byte ptr [edx],90h
;mov byte ptr [edx+1h],90h
;invoke MessageBox,NULL,addr szText,addr szModel,MB_OKCANCEL
;invoke Sleep,9000000
;初始化库需要的各种资源
.if ;初始化成功
;mov eax,TRUE
.else
;mov eax,FALSE
.endif
.elseif eax == DLL_THREAD_ATTACH
;释放库使用的资源
.elseif eax == DLL_THREAD_DETACH
;为新的线程分配资源
.elseif eax == DLL_PROCESS_DETACH
;为线程释放资源
.endif
ret DllEntry Endp _lanjie proc ;获得参数地址,并发送数据
push eax
push 1
push esi
push ecx
mov lpBuffers,esi
mov hSocket,ecx
mov eax,lpBuffers ;取得数据包长度
mov eax,[eax]
mov dwPackSize,eax
mov eax,lpBuffers ;取得数据包存放地址
mov eax,[eax+4h]
mov dwMemAdd,eax invoke WriteFile,hPipe,dwMemAdd,dwPackSize,offset dwSize,NULL
jmp [dwAddress] retn
_lanjie endp End DllEntry
这个代码在上次的代码基础上添加完成的,首先在DLL初始化的时候,加入
invoke CreateThread,NULL,0,offset _Recv,NULL,NULL,addr dwRecvThreadID
这个进程被创建后,开始执行_Recv函数。
下面再来看一下发送参数的管道服务器端,服务器端在和客户端建立管道连接后,即启动线程开始利用循环不断向管道写入01字节,当发送数据按钮被点击时,即取出编辑框2的内容写入管道。源代码依然采用E语言编写
.版本 2
.支持库 EThread
.支持库 EInterProcess
.程序集 窗口程序集1
.程序集变量 管道句柄, 整数型 .子程序 __启动窗口_创建完毕 .子程序 _按钮1_被单击
启动线程 (&读取封包, ) .子程序 读取封包
.局部变量 数据封包, 字节集
.局部变量 数值, 整数型
.局部变量 连接, 逻辑型
管道句柄 = 创建命名管道 (“masm”)
连接 = 监听命名管道 (管道句柄)
.判断循环首 (读命名管道 (管道句柄, 数据封包))
数值 = 取字节集长度 (数据封包)
.如果真 (数值 > 0)
编辑框1.加入文本 (查看字节集 (数据封包))
.如果真结束 .判断循环尾 ()
返回 () .子程序 _按钮2_被单击
启动线程 (&发送封包, ) .子程序 发送封包
.局部变量 发送封包, 字节集
.局部变量 状态, 整数型
.局部变量 长度, 整数型
发送封包 = 到字节集 (编辑框2.内容)
长度 = 取字节集长度 (发送封包)
.判断循环首 (长度 = 0) ;根据数据长度判断是否有数据将要发送
写命名管道 (管道句柄, { 1 }) ;没有数据被发送,即开始循环发送01字节
.判断循环尾 () 写命名管道 (管道句柄, 发送封包) ;如果数据长度不为0,说明有数据需要发送,即可开始发送字节集数据
返回 ()
以上代码本人已经测试通过,不过没有加入错误处理的相关代码。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课