【软件逆向-DEC/RPC协议与Windows服务创建浅析(银狐原始进程隐匿方式之一)】此文章归类为:软件逆向。
该方法是在一个银狐家族的样本中发现的,通过构造RPC数据包并发送请求至对应的RPC服务,能够绕过多个终端安全软件对CreatServices、NrdClientCall3等3环函数的Hook,从而规避服务创建监控和限制,构建出全白的进程链,使进程可信,且与原始进程断链,提升隐匿性,降低查杀阈值。
常见的RPC程序调用构建好必要的数据后,最终通过NrdClientCall系列函数进行通信,而这个函数中调用的下层的函数其实就是对RPC bind和RPC Request等数据包的处理,主要由NtAlpcSendWaitReceivePort等一系列NtAlpc函数完成。因为CreatServices和NrdClientCall等api会被杀软进行Hook,所以该方法未使用这些敏感API进行服务创建,而是自己构建符合格式的RPC数据包进行发包通信来创建服务,因此可以绕过部分只对RPC系列函数和服务创建启动等相关函数进行hook检测而未对RPC协议进行解析的安全软件。
一个完整的RCP调用的过程可以粗略分为Rpc绑定(连接)、Rpc绑定响应、Rpc请求、Rpc请求响应。通过逆向Services.exe、本地抓包以及查阅文档资料的方式可以获取到相关结构体和数据包。
RPC通信前需要先进行绑定,这个过程和TCP的握手有点类似,各自在本地维护一个内存结构,用于记录通信的状态,这里的内存结构称之为RpcConnectionStruct。此结构体维护Rpc通信的连接状态和数据输入输出的缓冲区,发送的RPC数据包由另外几个重要的数据结构和数据缓冲区构成:
RPC数据包的解析大致是这样:
RPC数据包发送(写入) 先写入RpcBase头,再写入RpcRequestHeader,最后跟上RpcConnectionStruct->bProcedureInputData(数据包)。
RPC数据包接受(读取) 先读取RpcBase头、判断Rpc通信的版本、数据包类型、数据包标记、数据帧长度是否等于实际读取长度。然后是RpcResponseHeader,最后是返回包的具体数据,这里面包含的是和指定RPC服务的数据。
下面我们具体了解一下这个通讯过程中需要用到的数据结构和相应的字段。
这个结构体维护Rpc通信的连接状态和一些必要数据,和TCP通信一样,在本地维护一段连接状态的数据结构,是自定义构建RPC通信的重要结构体之一,其中最重要是的hFile字段,这是一个句柄类型。打开一个RPC服务创建的命名管道可以获得该句柄,通过此句柄可以连接到指定的rpc服务。与RPC服务通信的请求body和响应body也放在这个结构体中,每次的消息发送与接受都要处理这个数据结构。下面是用C语言定义的结构体类型:
1 2 3 4 5 6 7 8 9 10 | struct RpcConnectionStruct { HANDLE hFile; // 文件句柄,用于RPC通信的管道 DWORD dwCallIndex; // 调用索引,用于标识RPC请求 DWORD dwInputError; // 输入错误标志 DWORD dwRequestInitialised; // 请求初始化标志 BYTE bProcedureInputData[MAX_PROCEDURE_DATA_LENGTH]; // 存储过程输入数据,后续通信的发送的数据都按格式排在这 DWORD dwProcedureInputDataLength; // 存储过程输入数据长度 BYTE bProcedureOutputData[MAX_PROCEDURE_DATA_LENGTH]; // 存储过程输出数据 DWORD dwProcedureOutputDataLength; // 存储过程输出数据长度 }; |
以银狐样本的中用到的SVCCTL服务为例,services.exe在主程序函数SvcctrlMain中注册了SVCCTL服务接口,其定义了一个命名管道符:\pip\ntsvcs和两种RPC底层通信协议序列:ncacn_np(命名管道协议序列)、 ncalrpcs(本地进程间通信协议序列)
一个RPC服务可以绑定多种协议序列,也可以只绑定某一种协议序列,这是实现相关的,没有定式。某接口绑定N种协议序列,就意味着有N条途径可以访问该接口。WIndows的DCE/MS RPC框架使用的下层协议序列有很多:
1 2 3 4 5 6 7 | + - - ncacn_ip_tcp(动态TCP口) | DCE / MS RPC - - + - - ncadg_ip_udp(动态UDP口) | + - - ncacn_np(固定的 139 、 445 / TCP口) | + - - ... ...(其它协议序列) |
对于命名管道(ncacn_np)的通信方式,一般使用CteateFile函数打开对应管道符获取返回值句柄,后面会将管道句柄填充到RpcConnectionStruct的hFile字段,用于维护RPC客户端的连接状态。
1 2 3 4 5 6 7 8 9 10 11 12 | HANDLE hFile = NULL; char szPipePath[512]; RpcConnectionStruct RpcConnection; // set pipe path memset (szPipePath, 0, sizeof (szPipePath)); _snprintf(szPipePath, sizeof (szPipePath) - 1, "\\\\.\\pipe\\%s" , pPipeName); // open rpc pipe hFile = CreateFileA(szPipePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); memset (( void *)&RpcConnection, 0, sizeof (RpcConnection)); RpcConnection.hFile = hFile; |
对于RPC服务来说,实现具体功能的是其不同接口下面的方法,因此想要远程调用指定服务功能除了需要管道句柄,还需要绑定对应功能接口、数据序列化与反序列化接口以及调用号。这些接口数据的定义在微软的官方文档、RPC服务程序的反编译、RPC通信抓包中可以找到部分。下面详解一下上面提到的另外几个关键结构体:RpcBaseHeaderStruct、RpcBindRequestHeaderStruct、RpcBindResponseHeaderStruct、RpcRequestHeaderStruct、RpcResponseHeaderStruct。
rpc基础头,包含rpc的版本,包类型,rpc包长度等字段。在rpc初次连接绑定阶段时,包长度就等于rpc基础头+rpc请求头的结构体大小,Bind包一般大小固定72,Bind_Ack包一般大小固定60。
1 2 3 4 5 6 7 8 9 10 | struct RpcBaseHeaderStruct { WORD wVersion; // 版本号5 BYTE bPacketType; // 包类型,Bind 11,响应Bind_ack 12,13代表失败 发送其他请求时为0 返回包该字段为2代表成功 BYTE bPacketFlags; // 包标志3 DWORD dwDataRepresentation; // 数据表示0x10 WORD wFragLength; // 片段长度(Base头+BindRequest头+RPC连接中的InputDataLengt) WORD wAuthLength; // 认证长度0 DWORD dwCallIndex; // 调用索引从0递增1,每个包都要自增,由RPC连接结构体维护并重新给base头 }; |
wVersion字段目前在DECRPC包中能看到的基本都是v5,较早的windows2000系统中有v4的版本,所以后续可以认为是一个固定值5。
bPacketType字段在初次发送绑定连接包时应设置为11(bind包),类似于TCP的SYN包,且当接受绑定返回包时需要校验bPacketType的值是否等于12(bind_ack),类似于TCP协议的ACK包,否则证明连接失败,此时的值一般是13。
其他字段的含义部分在注释中注明,详细信息可自行查找文档研究。
rpc绑定请求头,包含需要绑定接口的GUID、接口版本、通信协议、协议版本等信息。
以SVCCTL服务为例,services.exe在程序中使用RpcServerRegisterIf3函数注册了其接口信息,接口信息由一个名为RPC_SERVER_INTERFACE的结构体构成,详细的结构体信息会在后面补充信息中贴出,这里只关注该结构体中的两个重要字段:InterfaceId和TransferSyntax。这两个字段就是服务接口的GUID和序列化接口的GUID,我们拿到这两个GUID后填充到绑定请求头中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | struct RpcBindRequestContextEntryStruct { WORD wContextID; // 上下文ID,用于标识特定的接口或对象。0 WORD wTransItemCount; // 传输项目计数,通常用于指定传输语法的数量。1 BYTE bInterfaceUUID[16]; // 接口的全局唯一标识符(UUID),用于唯一标识一个接口。如367abb81-9844-35f1-ad32-98f038001003 DWORD dwInterfaceVersion; // 接口版本号,用于指定请求的接口版本。2 BYTE bTransferSyntaxUUID[16]; // 传输语法的UUID,用于指定数据传输时使用的协议格式。如8a885d04-1ceb-11c9-9fe8-08002b104860 DWORD dwTransferSyntaxVersion; // 传输语法版本号,用于指定传输语法的具体版本。2 }; struct RpcBindRequestHeaderStruct { WORD wMaxSendFrag; // 最大发送片段大小 默认4096 WORD wMaxRecvFrag; // 最大接收片段大小 默认4096 DWORD dwAssocGroup; // 关联组标识绑定包 0 返回包0x00012bee BYTE bContextCount; // 上下文数量 BYTE bAlign[3]; // 对齐填充 RpcBindRequestContextEntryStruct Context; // 上下文条目 }; |
这个绑定请求头由两个结构体构成,其中绑定请求上下文结构体存在4字节对齐,因此大小会有变化。部分字段的意义已在上面进行了注释,除了wContextID字段是双方在自增后存在RpcConnectionStruct头中维护外,其他字段都需要具体服务具体分析。
在前面对services.exe的反编译过程中已发现其他注册接口的接口体,其中就包含他的接口版本以及接口GUID:367abb81-9844-35f1-ad32-98f038001003以及序列化协议GUID:8a885d04-1ceb-11c9-9fe8-08002b104860,直接填充到RpcBindRequestContextEntryStruct结构体的字段中。
下面看绑定响应头的数据结构:RpcBindResponseHeaderStruct
这个结构体其实分为三段,由RpcBindResponseHeader1Struct+SecondaryAddrHeaderBlock+RpcBindResponseHeader2Struct构成。
结构体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | struct RpcBindResponseHeader1Struct { WORD wMaxSendFrag; WORD wMaxRecvFrag; DWORD dwAssocGroup; }; struct SecondaryAddrHeaderBlock { WORD ScndryAddrLen; BYTE ScndryAddr; BYTE bAlign[3]; // 4字节对齐填充 }; struct RpcBindResponseContextEntryStruct { WORD wResult; //返回值不为0代表bind失败,在解析时必须判断该项 WORD wAlign; //Ack reason BYTE bTransferSyntax[16]; //在wResult不为0时该字段表示序列化的协议 DWORD dwTransferSyntaxVersion; }; struct RpcBindResponseHeader2Struct { DWORD dwContextResultCount; //返回值为1代表存在上下文 RpcBindResponseContextEntryStruct Context; }; |
Bind_ack包如下:
ScndryAddr字段的长度不确定,存在0-65535这样2到6个字节的变化,需要4字节对齐
wResult字段在绑定成功时返回值为0,其他值参考如下:
当wResult字段不为0时,另外的三个字段就有了意义,wAlign字段会提供更详细的失败原因,具体代表什么含义可自行查相关文档。
而bTransferSyntax字段会在wResult字段等于2,wAlign字段等1时提供包含服务提供者支持的传输语法UUID,用于告知客户端服务端支持的传输语法。
关于RPC接口绑定相关的数据结构和对构建数据包比较重要的字段已做了详细解释,其他部分也做了简单的注释,了解这些已经就可以做简单的PRC绑定包构造了,如果遇到其他未知的信息可以在调试时结合相关文档具体分析。当接口绑定成功后,就可以发送指定接口的函数调用号用于执行我们想要执行的功能了,比如打开服务控制器、创建服务、开启服务等。
这个结构体中关注一个字段wProcedureNumber,这里存放接口函数的调用号,用于确定远程调用方法。
结构体如下:
1 2 3 4 5 6 | struct RpcRequestHeaderStruct { DWORD dwAllocHint; // 分配提示,用于服务器分配内存以存储回应 WORD wContextID; // 上下文ID,用于标识特定的接口或对象 WORD wProcedureNumber; // 过程编号,用于标识特定的远程过程 比如服务创建24 }; |
Requset包如下:
以SVCCTL服务接口的函数调用号为例,在service.exe中使用IDA可以找到其注册接口的调用号对应的虚函数表,例如wProcedureNumber=2,就是调用删除服务的RDeleteservice函数,用于删除指定服务,其次函数一次按下图中的虚函数表进行递增:
响应头结构体如下:
1 2 3 4 5 6 7 | struct RpcResponseHeaderStruct { DWORD dwAllocHint; // 分配提示,用于服务器分配内存以存储回应 WORD wContextID; // 上下文ID,用于标识特定的接口或对象 BYTE bCancelCount; // 取消计数,用于标识请求是否被取消 BYTE bAlign[1]; // 对齐填充,用于确保结构体按照特定的边界对齐 }; |
Response包如下:
了解完这些必要的结构体和在RPC数据包中的形式后可以传送对应的请求data了,以我们要复现的服务创建和启动为例,在获得服务管理对象后,data部分带上服务管理对象加上函数调用号就可以完成执行函数的调用,除了在创建服务时需要传送比较多的数据,涉及服务名、显示名、服务启动方式、服务程序路径等外,其他在上面看到那个虚函数表中大多数函数的调用只需要在RpcRequest包的data部分加入服务管理器对象和调用号即可,下图是C语言模拟发送服务创建包的需要处理的结构体部分代码(copy自Cmdexec):
对于服务路径指向的程序,exe需要实现ServiceMain函数,dll程序同样需要实现该函数并且进行函数导出。
最终的效果如下图,成功创建了testservice服务,服务程序Service-loader.exe以SYSTEM权限运行,这里的Service-loader.exe就类比银狐用的第一阶段的加载器,后续的远程下载、文件释放、维权、加载文件到内存、解密、线程注入、com组件注册、白加黑等操作,本文主要是对其前置隐匿断链阶段的服务创建和启动方式做了详细分析和复现,后续的远控的常规操作可参考其他厂家对银狐等家族的后门分析文章。
下面是一些补充信息,遇到的一些复现问题和其他相关数据结构的总结,有前置知识储备的可以忽略下面的内容。
创建新的服务后会在:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services自动生成以你的服务名命名的键,以及imgepath等值。
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost
如果要通过svchost调用来启动的服务,就一定要在
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost下有该服务名,这可以通过如下方式来实现:
(1) 添加一个新的服务组,在组里添加服务名;
(2) 在现有组里添加服务名;
(3) 直接使用现有服务组里的一个服务名,但本机没有安装的该服务;
(4) 修改现有服务组里的现有服务,把它的ServiceDll指向自己。
前两种可以被正常服务使用,如使用第1种方式,启动其服务要创建新的svchost进程;第2种方式如果该组服务已经运行,安装后不能立刻启动服务,因为svchost启动后已经把该组信息保存在内存里,并调用API StartServiceCtrlDispatcher() 为该组所有服务注册了调度处理函数,新增加的服务不能再注册调度处理函数,需要重启计算机或者该组的svchost进程。 后两种可能被后门使用,尤其是最后一种,没有添加服务,只是改了注册表里一项设置,从服务管理控制台又看不出来,如果作为后门还是很隐蔽的。比如EventSystem服务,缺省是指向es.dll,如果把ServiceDll改为EventSystem.dll就很难发现。 服务的安装除了调用CreateService()创建服务之外,还需要设置服务的ServiceDll,如果使用前2种还要设置svchost的注册表选项,在卸载时也最好删除增加的部分。
客户端Rpc调用在Ring3能看到的最底层的函数,重要的数据结构都在这个函数的参数中
服务端Rpc调用也有个相对应的NdrClientCall3(NdrServerCallx)
1 2 3 4 5 6 | CLIENT_CALL_RETURN RPC_VAR_ENTRY NdrClientCall3( MIDL_STUBLESS_PROXY_INFO *pProxyInfo, //指向该 RPC 调用的代理信息的指针 unsigned long nProcNum, //要调用的存储过程编号/操作数的值 void *pReturnValue, ... ); |
在新的IDL文件生成的接口代码中使用的NdrClientCall2函数替代了,且参数略有变化,多了IDL_handle
1 2 3 4 | NdrClientCall2( ( PMIDL_STUB_DESC )&HelloCrispr_StubDesc, (PFORMAT_STRING) &rpcDemo__MIDL_ProcFormatString.Format[0], ( unsigned char * )&IDL_handle); |
HelloCrispr_StubDesc参数是pProxyInfo指向的结构体的第一个元素_MIDL_STUB_DESC结构体,相当于去掉了一级指针。
常规创建自己的rpc服务端和对应的客户端,首先需要一个idl接口描述,然后使用MIDL工具生成对应的客户端以及服务端的Stub文件。生成完成后将对应的xxs.c、xxc.c、xx.h文件分别放到s端和c端进行编译,必要的数据结构一在文件中生成了,只要直接传参调用就行,比如s端使用RpcServerRegisterIf函数进行接口注册,传入RPC_IF_HANDLE。
1 2 3 4 5 6 7 8 9 10 | RPCRTAPI RPC_STATUS RPC_ENTRY RpcServerRegisterIf ( _In_ RPC_IF_HANDLE IfSpec, _In_opt_ UUID __RPC_FAR * MgrTypeUuid, _In_opt_ RPC_MGR_EPV __RPC_FAR * MgrEpv ); typedef void __RPC_FAR * RPC_IF_HANDLE; |
服务端需要使用RpcServerRegisterIf 系类函数进行接口注册,注册后的接口信息由RPC运行时库负责维护。其中,最为主要的结构为 RPC_IF_HANDLE,在服务端,这个指针结构为:RPC_SERVER_INTERFACE
1 2 3 4 5 6 7 8 9 10 11 12 | typedef struct _RPC_SERVER_INTERFACE { unsigned int Length; RPC_SYNTAX_IDENTIFIER InterfaceId; RPC_SYNTAX_IDENTIFIER TransferSyntax; PRPC_DISPATCH_TABLE DispatchTable; unsigned int RpcProtseqEndpointCount; PRPC_PROTSEQ_ENDPOINT RpcProtseqEndpoint; //包含RpcProtocolSequence和Endpoint,指定协议序列和端口(字符串指针) RPC_MGR_EPV __RPC_FAR *DefaultManagerEpv; void const __RPC_FAR *InterpreterInfo; //指向_MIDL_SERVER_INFO_结构体 unsigned int Flags ; } RPC_SERVER_INTERFACE, __RPC_FAR * PRPC_SERVER_INTERFACE; |
在服务端RPC_IF_HANDLE指针偏移80字节位置拿到InterpreterInfo 成员,该成员是一个指向_MIDL_SERVER_INFO_结构体的指针,偏移8字节(64位系统)的位置是_MIDL_SERVER_INFO_结构体的第二个元素DispatchTable,这是一个虚函数表,存储着不同的和服务相关的虚函数指针。如服务创建、删除、启动等函数。
_MIDL_SERVER_INFO_结构体
1 2 3 4 5 6 7 8 9 10 11 | typedef struct _MIDL_SERVER_INFO_ { PMIDL_STUB_DESC pStubDesc; //指向自己的上上层指针 const SERVER_ROUTINE * DispatchTable; //包含提供的函数 PFORMAT_STRING ProcString; const unsigned short * FmtStringOffset; const STUB_THUNK * ThunkTable; PRPC_SYNTAX_IDENTIFIER pTransferSyntax; ULONG_PTR nCount; PMIDL_SYNTAX_INFO pSyntaxInfo; } MIDL_SERVER_INFO, *PMIDL_SERVER_INFO; |
在客户端也存在着该指针RPC_IF_HANDLE和服务端RPC_SERVER_INTERFACE结构体一毛一样的结构体RPC_CLIENT_INTERFACE
1 2 3 4 5 6 7 8 9 10 11 | typedef struct _RPC_CLIENT_INTERFACE { unsigned int Length; RPC_SYNTAX_IDENTIFIER InterfaceId; RPC_SYNTAX_IDENTIFIER TransferSyntax; PRPC_DISPATCH_TABLE DispatchTable; unsigned int RpcProtseqEndpointCount; PRPC_PROTSEQ_ENDPOINT RpcProtseqEndpoint; ULONG_PTR Reserved; void const *InterpreterInfo; unsigned int Flags; } RPC_CLIENT_INTERFACE, *PRPC_CLIENT_INTERFACE; |
_MIDL_STUB_DESC结构体是的第一元素是RpcInterfaceInformation,这其实就是RPC_IF_HANDLE指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | typedef struct _MIDL_STUB_DESC { void *RpcInterfaceInformation; //RPC_IF_HANDLE RPC_SERVER_INTERFACE结构体 void * )( size_t ) *(pfnAllocate; void ()( void *) * pfnFree; union { handle_t *pAutoHandle; handle_t *pPrimitiveHandle; PGENERIC_BINDING_INFO pGenericBindingInfo; } IMPLICIT_HANDLE_INFO; //RPC_BINDING_HANDLE const NDR_RUNDOWN *apfnNdrRundownRoutines; const GENERIC_BINDING_ROUTINE_PAIR *aGenericBindingRoutinePairs; const EXPR_EVAL *apfnExprEval; const XMIT_ROUTINE_QUINTUPLE *aXmitQuintuple; const unsigned char *pFormatTypes; int fCheckBounds; unsigned long Version; MALLOC_FREE_STRUCT *pMallocFreeStruct; long MIDLVersion; const COMM_FAULT_OFFSETS *CommFaultOffsets; const USER_MARSHAL_ROUTINE_QUADRUPLE *aUserMarshalQuadruple; const NDR_NOTIFY_ROUTINE *NotifyRoutineTable; ULONG_PTR mFlags; const NDR_CS_ROUTINES *CsRoutineTables; void *ProxyServerInfo; |
1 2 3 4 5 6 7 8 | typedef struct _MIDL_STUBLESS_PROXY_INFO { PRPC_STUB_DESC pStubDesc; //指向MIDL_STUB_DESC结构体 unsigned char *ProcFormatString; USHORT *FormatStringOffset; PRPC_SYNTAX_IDENTIFIER pTransferSyntax; ULONG nCount; struct _MIDL_SYNTAX_INFO *pSyntaxInfo; } MIDL_STUBLESS_PROXY_INFO, *PMIDL_STUBLESS_PROXY_INFO; |
这篇文章主要是笔记整理的,可能略微有点乱,看官们多担待。
最后贴一下参考的部分大佬们文章原链接:
https://bbs.kanxue.com/thread-251158-1.htm
https://bbs.kanxue.com/thread-257115.htm
c7aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6j5%4A6Q4x3X3f1$3x3e0N6Q4x3X3g2U0L8W2)9J5c8X3&6W2N6s2N6G2M7X3E0Q4x3V1j5J5x3o6l9$3x3o6t1I4y4o6p5J5y4e0W2Q4x3X3g2@1P5s2b7`.
611K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6^5P5W2)9J5k6h3q4D9K9i4W2#2L8W2)9J5k6h3y4G2L8g2)9J5c8Y4c8Q4x3V1j5I4x3e0x3I4x3#2)9K6c8Y4c8A6L8h3g2Q4y4h3k6Q4y4h3j5I4x3K6p5I4i4K6y4p5b7%4p5H3P5q4t1%4x3q4q4p5f1h3g2^5L8o6k6*7c8K6N6w2L8@1c8Q4x3U0f1K6c8p5M7^5d9f1S2A6N6p5W2H3M7r3S2c8x3%4R3`.
790K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2X3M7X3g2W2M7$3W2G2L8W2)9J5k6h3y4G2L8g2)9J5c8X3q4J5N6r3W2U0L8r3g2Q4x3V1j5&6x3o6f1%4z5e0l9K6x3K6V1J5i4K6u0r3
08fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4q4I4i4K6g2X3y4o6x3K6x3K6t1H3x3e0m8Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3k6r3g2@1j5h3W2D9M7#2)9J5c8U0p5J5x3U0R3@1x3e0j5J5x3R3`.`.
f1dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2Z5N6h3!0J5L8$3&6Y4i4K6u0W2j5$3&6Q4x3V1k6V1L8$3y4#2L8h3g2F1N6q4)9J5c8Y4c8W2j5$3S2Q4x3V1k6$3K9i4u0Q4y4h3k6J5k6i4m8G2M7Y4c8Q4x3V1j5I4y4K6R3@1
更多【软件逆向-DEC/RPC协议与Windows服务创建浅析(银狐原始进程隐匿方式之一)】相关视频教程:www.yxfzedu.com