首先声明本文为菜鸟级,高手就不要看了,呵呵。关于如何控制其它程序内部函数的问题,我在网上找了很久也没有找到比较全面的技术资料和文档。于是我开始翻看《WINDOWS核心编程》及《WINDOWS网络编程》两本资料,并且进行了实际测试。
    首先说下大体思路,如果要控制一个进程的内部函数,我认为功能上大体可分为两个部分。第一部分需要控制的就是函数的参数,能够取出函数的参数;第二部分就是能够根据自己的需要调用这个函数。今天我想先根据我的经验给大家介绍下第一部分的内容,有说的不对的地方欢迎大家指正。
    要控制一个进程,方式有很多,比如HOOK等技术,但是我个人认为那些技术不够灵活,在功能上也不够全面,也可能是我对HOOK的理解和应用不够深入吧。我采取的方法是利用创建远程线程的办法,插入一个DLL到目标进程,然后利用DLL内部代码捕获指定函数的指定参数,并且传送到我们自己的一个进程。具体分为以下几部进行的:
1.首先建立一个EXE文件用于插入DLL到目标进程。
2.DLL被成功插入后,立即执行其初始化代码。
3.捕获函数参数。
4.利用管道通信技术,把参数值传回我们自己的程序。
以下我分步具体讲解实施细节。
1.首先建立一个EXE文件用于插入DLL到目标进程。
这个过程在《WINDOWS核心编程》里面介绍的比较详细,不过里面是C语言代码,我是利用汇编语言实现的。首先我们需要找到目标进程的窗口,然后取得进程的句柄,然后以我们需要的方式打开进程,然后在目标进程内创建一端内存,用来存放我们的DLL名称字符串,然后再获得Kernel32.dll的LoadLibraryA函数的内存入口地址,然后利用这个函数装载我们的DLL到目标进程,然后利用CreateRemoteThread函数在目标进程中创建一个线程,用来执行DLL的初始化代码。
汇编源代码如下:
               .386

               .model flat,stdcall

               option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

include        windows.inc

include        user32.inc

includelib     user32.lib

include        kernel32.inc

includelib     kernel32.lib



;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

               .data

hGameHandle    dd     ?    ;进程句柄
hGameClass  dd  ?  ;游戏窗口类名
dwProcessID  dd  ?

szBuffer       db     256 dup (?)
dwMemAdd  dd  ?  ;创建的内存空间的地址
dwSizeDllName  dd  ?  ;欲插入的DLL文件名长度
dwLoadLibraryW  dd  ?   ;LoadLibraryW函数地址
dwRemoteThreadID  dd  ?  ;创建的远程线程的ID
dwExitCode  dd  ?

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

               .const

szCaption      db     'SendMessage',0

szStart        db     'Press OK to start SendMessage, param: %d',0

szReturn       db     'SendMessage returned!',0


szGameClass  db  'FSOnline Class',0

szText         db     'Text send to other windows',0

szNotFound     db     'Receive Message Window not found!',0
szError    db  '无法打开进程',0
szSucceed  db  '打开进程成功',0
szMemError  db  '申请内存失败',0
szMemSucceed  db  '申请内存成功',0
szExit    db  '线程退出,0
szFileName  db  'c:\\mylib.dll',0
szKernel32  db  'Kernel32.dll',0
szLoadLibraryW  db  'LoadLibraryA',0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

               .code


start:

              invoke  FindWindow,addr szGameClass,NULL    ;查找目标进程窗口
              mov  hGameClass,eax
        invoke GetWindowThreadProcessId,hGameClass,addr dwProcessID  ;取得目标进程ID
               .if    dwProcessID
                     

                      invoke  wsprintf,addr szBuffer,addr szStart,dwProcessID

                      invoke  MessageBox,NULL,offset szBuffer,\

                              offset szCaption,MB_OK
                              
                      
                     
                      invoke OpenProcess,PROCESS_QUERY_INFORMATION or PROCESS_CREATE_THREAD or \  ;以指定权限打开目标进程
                          PROCESS_VM_OPERATION or PROCESS_VM_WRITE,FALSE, dwProcessID
                ; Required by Alpha// For CreateRemoteThread// For VirtualAllocEx/VirtualFreeEx// For WriteProcessMemory  

             .if eax==NULL
               invoke  MessageBox,NULL,addr szError,addr szCaption,MB_OK
               invoke ExitProcess,NULL  
             .else
               mov hGameHandle,eax
               invoke  MessageBox,NULL,addr szSucceed,addr szCaption,MB_OK
             .endif  
               
             mov eax,sizeof szFileName
             mov dwSizeDllName,eax
             invoke VirtualAllocEx,hGameHandle, NULL, dwSizeDllName, MEM_COMMIT,PAGE_READWRITE ;创建内存地址空间,用来传递DLL名称字符串
             
             .if eax==NULL
               invoke  MessageBox,NULL,addr szMemError,addr szCaption,MB_OK
               invoke ExitProcess,NULL  
             .else
               mov dwMemAdd,eax
               invoke  MessageBox,NULL,addr szMemSucceed,addr szCaption,MB_OK
             .endif  
             
      invoke WriteProcessMemory,hGameHandle, dwMemAdd,addr szFileName,\ 
               dwSizeDllName, NULL  ;目标DLL名称写入进程内存
             invoke GetModuleHandle,addr szKernel32
             invoke GetProcAddress,eax,offset szLoadLibraryW
           
      mov dwLoadLibraryW,eax
      
      invoke CreateRemoteThread,hGameHandle, NULL, 0,dwLoadLibraryW, dwMemAdd, 0, NULL   ;创建线程
      mov dwRemoteThreadID,eax
      
      invoke WaitForSingleObject,dwRemoteThreadID, INFINITE ;等待线程结束并释放资源
      invoke  MessageBox,NULL,addr szExit,addr szCaption,MB_OK
      invoke GetExitCodeThread,dwRemoteThreadID, dwExitCode
      invoke CloseHandle,dwRemoteThreadID


      
      


               .else

                      invoke  MessageBox,NULL,offset szNotFound,\

                      offset  szCaption,MB_OK

               .endif

               invoke ExitProcess,NULL

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

               end    start

我这个人比较懒,很多出错代码没有写,还有些用于调试的代码没有删除,呵呵。
下面我们再来看看插入到目标进程内的DLL代码,这段代码的初始化代码在远程线程建立的时候即开始运行。我目前要控制的函数是目标进程的WSASend函数,不要以为这种方法只能控制API,等你看完你就明白,只要是目标进程使用的函数,不管是内部的还是外部的,都可以控制。我们的目标是WSASend函数的第2个参数,因为这个参数记录着要发送数据包的长度和数据包的内存地址,我们先来看下用OD打开目标进程后,它调用WSASend函数的过程。
005609D2    6A 00           push    0
005609D4    8906            mov     [esi], eax
005609D6    03E9            add     ebp, ecx
005609D8    6A 00           push    0
005609DA    8D4424 18       lea     eax, [esp+18]
005609DE    6A 00           push    0
005609E0    896E 04         mov     [esi+4], ebp
005609E3    2BF9            sub     edi, ecx
005609E5    8B8B FC010000   mov     ecx, [ebx+1FC]
005609EB    50              push    eax
005609EC    6A 01           push    1
005609EE    56              push    esi
005609EF    51              push    ecx
005609F0    897C24 30       mov     [esp+30], edi
005609F4    FF15 5CC75700   call    [57C75C]                         ; WS2_32.WSASend

可以看到我们要获得的函数的参数在ESI中保存,那么我们插入DLL的初始化代码就把
005609EB    50              push    eax
这一条指令改为跳转到我们DLL的获取参数的代码的地址处,为什么选从这里开始呢,因为在这里之前,ESI已经被付值,并且JMP指令占用的是5字节内存,正好到
005609F0    897C24 30       mov     [esp+30], edi
这里,我们的代码取出ESI的数值后,在JMP到005609F0,即可不影响这个进程的正常工作。好了,看下我们的DLL代码:
    .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
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
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  ?    ;模拟发送的数据长度
  .const
szText  db  '载入成功',0
szProc  db  '_lanjie',0
szModel  db  'mylib.dll',0
szPipeName  db  '\\.\Pipe\masm',0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    .code

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
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
;修改005609EB 地方的指令为跳转指令                        
                        
 invoke WaitNamedPipe,addr szPipeName,500h    ;连接我们管道通信的服务器,并获得管道句柄
 
 invoke CreateFile,addr szPipeName,GENERIC_READ or GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL
 mov hPipe,eax
                             



                .elseif eax ==  DLL_THREAD_ATTACH

                    ;释放库使用的资源

                .elseif eax ==  DLL_THREAD_DETACH

                   ;为新的线程分配资源

                .elseif eax ==  DLL_PROCESS_DETACH

                    ;为线程释放资源

                .endif

                ret


DllEntry  Endp




_lanjie  proc    ;我们处理函数参数的代码
  
push eax               ;因为跳转指令覆盖掉了这4条指令,所以在这里我们需要把这四条指令加入
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]
;跳回005609F0处继续进程运行

  retn

_lanjie endp


End     DllEntry
下面就是这个DLL插入到目标进程后,目标进程调用WSASend处的代码
005609D2    6A 00           push    0
005609D4    8906            mov     [esi], eax
005609D6    03E9            add     ebp, ecx
005609D8    6A 00           push    0
005609DA    8D4424 18       lea     eax, [esp+18]
005609DE    6A 00           push    0
005609E0    896E 04         mov     [esi+4], ebp
005609E3    2BF9            sub     edi, ecx
005609E5    8B8B FC010000   mov     ecx, [ebx+1FC]
005609EB  - E9 C3064404     jmp     mylib._lanjie
005609F0    897C24 30       mov     [esp+30], edi
005609F4    FF15 5CC75700   call    [57C75C]                         ; WS2_32.WSASend

怎么样,看到了吧
005609EB  - E9 C3064404     jmp     mylib._lanjie
调到了我们的代码处。

然后再来看下我们建立的用于接收参数信息的进程管道通信的服务器端,这一端我使用的是E语言编写的代码,E语言操作简单,不用写那么多API即可,呵呵
.版本 2
.版本 2
.支持库 EThread
.支持库 EInterProcess

.程序集 窗口程序集1

.子程序 __启动窗口_创建完毕



.子程序 _按钮1_被单击

启动线程 (&读取封包, )



.子程序 读取封包
.局部变量 管道句柄, 整数型
.局部变量 数据封包, 字节集
.局部变量 数值, 整数型
.局部变量 连接, 逻辑型

管道句柄 = 创建命名管道 (“masm”)
连接 = 监听命名管道 (管道句柄)

.判断循环首 (读命名管道 (管道句柄, 数据封包))
    数值 = 取字节集长度 (数据封包)
    .如果真 (数值 > 0)
        编辑框1.加入文本 (查看字节集 (数据封包))
    .如果真结束


.判断循环尾 ()

返回 ()



其实程序很简单,就是创建一个通信管道,然后监听,当有连接后,开始循环读取管道内数据,当数据长度不为零时就把数据输出到一个编辑框里。
也可能因为我是菜鸟,所以只能用这种方式控制其它进程内的函数,如果哪位大侠有更好的更简单的控制办法,请告诉我好么?

今天用了一天的时间在调试第二部分代码,本以为在昨天代码的基础上实现第二部分功能应该比较简单,没想到还是费了不少力气,呵呵。
    好了,还是先说下大体思路,如果需要调用其它进程的内部函数做自己的事情,那么必须先把需要调用的函数的参数传递给目标函数,要实现这个功能我还是选用管道通信技术,毕竟上一部分已经建立了一个管道。然后由我们的程序把参数利用管道发给已经插入到目标进程中的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,说明有数据需要发送,即可开始发送字节集数据

返回 ()

以上代码本人已经测试通过,不过没有加入错误处理的相关代码。