Ring3下Dll注入方法整理汇总

1.dll劫持,粗略整理了下,可以劫持的dll有(持续更新):
lpk.dll、usp10.dll、msimg32.dll、midimap.dll、ksuser.dll、comres.dll、ddraw.dll
以lpk为例,在win7下由于lpk被加入KnownDLLs且该注册表值不可修改,使得lpk强制从系统目录加载,
不过可以将lpk.dll加入ExcludeFromKnownDlls来解决,具体可以创建一个lpk.reg文件:
 
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager]
"ExcludeFromKnownDlls"=hex(7):6c,00,70,00,6b,00,2e,00,64,00,6c,00,6c,00,00,00,\
00,00
 
成功导入后需要重新启动电脑才能生效。
参考:http://support.microsoft.com/?scid=kb;en-us;164501&x=4&y=12
另外win7下的lpk在编写方面需要注意:
WIN7有的程序调用LPK.DLL的LpkInitialize输出函数在LPK的初始化前面.
要在LpkInitialize这个函数中加入一些处理,并且这部分代码不能加密.
因此为了兼容各个系统,可以在DllMain和LpkInitialize里均做判断,如果没有初始化就进行初始化。下面贴出完整代码:
 
// lpk.cpp : Defines the entry point for the DLL application.
//


// 头文件
#include "stdafx.h"





// 导出函数
#pragma comment(linker, "/EXPORT:LpkInitialize=_AheadLib_LpkInitialize,@1")
#pragma comment(linker, "/EXPORT:LpkTabbedTextOut=_AheadLib_LpkTabbedTextOut,@2")
#pragma comment(linker, "/EXPORT:LpkDllInitialize=_AheadLib_LpkDllInitialize,@3")
#pragma comment(linker, "/EXPORT:LpkDrawTextEx=_AheadLib_LpkDrawTextEx,@4")
//#pragma comment(linker, "/EXPORT:LpkEditControl=_AheadLib_LpkEditControl,@5")
#pragma comment(linker, "/EXPORT:LpkExtTextOut=_AheadLib_LpkExtTextOut,@6")
#pragma comment(linker, "/EXPORT:LpkGetCharacterPlacement =_AheadLib_LpkGetCharacterPlacement ,@7")
#pragma comment(linker, "/EXPORT:LpkGetTextExtentExPoint=_AheadLib_LpkGetTextExtentExPoint,@8")
#pragma comment(linker, "/EXPORT:LpkPSMTextOut=_AheadLib_LpkPSMTextOut,@9")
#pragma comment(linker, "/EXPORT:LpkUseGDIWidthCache=_AheadLib_LpkUseGDIWidthCache,@10")
#pragma comment(linker, "/EXPORT:ftsWordBreak=_AheadLib_ftsWordBreak,@11")





// 宏定义
#define EXTERNC extern "C"
#define NAKED __declspec(naked)
#define EXPORT __declspec(dllexport)

#define ALCPP EXPORT NAKED
#define ALSTD EXTERNC EXPORT NAKED void __stdcall
#define ALCFAST EXTERNC EXPORT NAKED void __fastcall
#define ALCDECL EXTERNC NAKED void __cdecl

//LpkEditControl导出的是数组,不是单一的函数(by Backer)
EXTERNC void __cdecl AheadLib_LpkEditControl(void);    
EXTERNC __declspec(dllexport) void (*LpkEditControl[14])() = {AheadLib_LpkEditControl};    


//添加全局变量
BOOL g_bInited = FALSE;

 


// AheadLib 命名空间
namespace AheadLib
{
       HMODULE m_hModule = NULL;       // 原始模块句柄
      
       // 加载原始模块
       BOOL WINAPI Load()
       {
               TCHAR tzPath[MAX_PATH];
               TCHAR tzTemp[MAX_PATH * 2];
              
               GetSystemDirectory(tzPath, MAX_PATH);
               lstrcat(tzPath, TEXT("\\lpk.dll"));
               OutputDebugString(tzPath);
               m_hModule=LoadLibrary(tzPath);
               if (m_hModule == NULL)
               {
                       wsprintf(tzTemp, TEXT("无法加载 %s,程序无法正常运行。"), tzPath);
                       MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP);
               };
              
               return (m_hModule != NULL);      
       }
      
       // 释放原始模块
       VOID WINAPI Free()
       {
               if (m_hModule)
               {
                       FreeLibrary(m_hModule);
               }
       }
      
       // 获取原始函数地址
       FARPROC WINAPI GetAddress(PCSTR pszProcName)
       {
               FARPROC fpAddress;
               CHAR szProcName[16];
               TCHAR tzTemp[MAX_PATH];
              
               fpAddress = GetProcAddress(m_hModule, pszProcName);
               if (fpAddress == NULL)
               {
                       if (HIWORD(pszProcName) == 0)
                       {
                               wsprintf(szProcName, "%d", pszProcName);
                               pszProcName = szProcName;
                       }
                      
                       wsprintf(tzTemp, TEXT("无法找到函数 %hs,程序无法正常运行。"), pszProcName);
                       MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP);
                       ExitProcess(-2);
               }
              
               return fpAddress;
       }
}
using namespace AheadLib;
 


//函数声明
void WINAPIV Init(LPVOID pParam);


void WINAPIV Init(LPVOID pParam)
{
       //在这里添加DLL加载代码
       return;
}


// 入口函数
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
       if (dwReason == DLL_PROCESS_ATTACH)
       {
               DisableThreadLibraryCall s(hModule);
               if ( g_bInited==FALSE ){
                       Load();
                       g_bInited = TRUE;
               }
              
               //LpkEditControl这个数组有14个成员,必须将其复制过来      
               memcpy((LPVOID)(LpkEditControl+1), (LPVOID)((int*)GetAddress("LpkEditControl") + 1),52);   
               _beginthread(Init,NULL,NULL);
       }
       else if (dwReason == DLL_PROCESS_DETACH)
       {
               Free();
       }
       return TRUE;
}





// 导出函数
ALCDECL AheadLib_LpkInitialize(void)
{
       if ( g_bInited==FALSE ){
               Load();
               g_bInited = TRUE;
       }
       GetAddress("LpkInitialize");
       __asm JMP EAX;
}





// 导出函数
ALCDECL AheadLib_LpkTabbedTextOut(void)
{
       GetAddress("LpkTabbedTextOut");
       __asm JMP EAX;
}





// 导出函数
ALCDECL AheadLib_LpkDllInitialize(void)
{
       GetAddress("LpkDllInitialize");
       __asm JMP EAX;
}





// 导出函数
ALCDECL AheadLib_LpkDrawTextEx(void)
{
       GetAddress("LpkDrawTextEx");
       __asm JMP EAX;
}





// 导出函数
ALCDECL AheadLib_LpkEditControl(void)
{
       GetAddress("LpkEditControl");
       __asm jmp DWORD ptr [EAX];//这里的LpkEditControl是数组,eax存的是函数指针
}





// 导出函数
ALCDECL AheadLib_LpkExtTextOut(void)
{
       GetAddress("LpkExtTextOut");
       __asm JMP EAX;
}





// 导出函数
ALCDECL AheadLib_LpkGetCharacterPlacement (void)
{
       GetAddress("LpkGetCharacterPlacement ");
       __asm JMP EAX;
}





// 导出函数
ALCDECL AheadLib_LpkGetTextExtentExPoint(void)
{
       GetAddress("LpkGetTextExtentExPoint");
       __asm JMP EAX;
}





// 导出函数
ALCDECL AheadLib_LpkPSMTextOut(void)
{
       GetAddress("LpkPSMTextOut");
       __asm JMP EAX;
}





// 导出函数
ALCDECL AheadLib_LpkUseGDIWidthCache(void)
{
       GetAddress("LpkUseGDIWidthCache");
       __asm JMP EAX;
}





// 导出函数
ALCDECL AheadLib_ftsWordBreak(void)
{
       GetAddress("ftsWordBreak");
       __asm JMP EAX;
}

 

 
2.通过CreateRemoteThread创建远程线程。
XP以下使用代码:
 
BOOL WINAPI RemoteLoadLibrary(LPCTSTR pszDllName, DWORD dwProcessId)
{
       // 打开目标进程
       HANDLE hProcess = ::OpenProcess(
               PROCESS_VM_WRITE|PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION, FALSE, dwProcessId);
       if(hProcess == NULL)
               return FALSE;


       // 在目标进程申请空间,存放字符串pszDllName,作为远程线程的参数
       int cbSize = (::lstrlen(pszDllName) + 1);
       LPVOID lpRemoteDllName = ::VirtualAllocEx(hProcess, NULL, cbSize, MEM_COMMIT, PAGE_READWRITE);
       ::WriteProcessMemory(hProcess, lpRemoteDllName, pszDllName, cbSize, NULL);

       // 取得LoadLibraryA函数的地址,我们将以它作为远程线程函数启动
       HMODULE hModule=::GetModuleHandle (_T("kernel32.dll"));
       LPTHREAD_START_ROUTINE pfnStartRoutine =
               (LPTHREAD_START_ROUTINE)::GetProcAddress(hModule, "LoadLibraryA");


       // 启动远程线程
       HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnStartRoutine, lpRemoteDllName, 0, NULL);
       if(hRemoteThread == NULL)
       {
               ::CloseHandle(hProcess);
               return FALSE;


       }

       ::CloseHandle(hRemoteThread);
       ::CloseHandle(hProcess);

       return TRUE;
}
 
这段代码在vista,win7下不能成功,需要改进,参考:http://bbs.pediy.com/showthread.php?t=101469&highlight=Vista+Win7+CreateRemoteThread
我参考上面资料和代码,稍作整理使之编译通过并能使用,目标进程打开时最好使用PROCESS_ALL_ACCESS权限。
vista的较为简单些,只要修改一个内存里的数值,这里不再实现。
 
typedef struct _CLIENT_ID {
       HANDLE UniqueProcess;
       HANDLE UniqueThread;
} CLIENT_ID,*PCLIENT_ID;

typedef struct _INITIAL_TEB
{
       PVOID PreviousStackBase;
       PVOID PreviousStackLimit;
       PVOID StackBase;
       PVOID StackLimit;
       PVOID AllocatedStackBase;
} INITIAL_TEB, *PINITIAL_TEB;


typedef NTSTATUS (NTAPI *TZwAllocateVirtualMemory )(
                                                                 __in         HANDLE ProcessHandle,
                                                                 __inout   PVOID *BaseAddress,
                                                                 __in         ULONG_PTR ZeroBits,
                                                                 __inout   PSIZE_T RegionSize,
                                                                 __in         ULONG AllocationType,
                                                                 __in         ULONG Protect
                                                                 );

static TZwAllocateVirtualMemory  ZwAllocateVirtualMemory = (TZwAllocateVirtualMemory )GetProcAddress(GetModuleHandle("ntdll.dll"),"ZwAllocateVirtualMemory");

typedef NTSYSAPI NTSTATUS (NTAPI *TZwWriteVirtualMemory)       (       IN HANDLE         ProcessHandle,
                                                                                                 IN PVOID         BaseAddress,
                                                                                                 IN PVOID         Buffer,
                                                                                                 IN SIZE_T         NumberOfBytesToWrite,
                                                                                                 OUT PSIZE_T         NumberOfBytesWritten        
                                                                                                 );
static TZwWriteVirtualMemory ZwWriteVirtualMemory = (TZwWriteVirtualMemory)GetProcAddress(GetModuleHandle("ntdll.dll"),"ZwWriteVirtualMemory");

typedef NTSYSAPI NTSTATUS (NTAPI *TZwProtectVirtualMemory)       (       IN HANDLE         ProcessHandle,
                                                                                                 IN PVOID *         BaseAddress,
                                                                                                 IN SIZE_T *         NumberOfBytesToProtect,
                                                                                                 IN ULONG         NewAccessProtection,
                                                                                                 OUT PULONG         OldAccessProtection        
                                                                                                 );
static TZwProtectVirtualMemory ZwProtectVirtualMemory = (TZwProtectVirtualMemory)GetProcAddress(GetModuleHandle("ntdll.dll"),"ZwProtectVirtualMemory");

typedef NTSYSAPI NTSTATUS (NTAPI *TZwGetContextThread)       (       IN HANDLE         ThreadHandle,
                                                                                         OUT PCONTEXT         Context        
                                                                                         );
static TZwGetContextThread ZwGetContextThread = (TZwGetContextThread)GetProcAddress(GetModuleHandle("ntdll.dll"),"ZwGetContextThread");

typedef NTSYSAPI NTSTATUS (NTAPI *TZwCreateThread)       (       OUT PHANDLE         ThreadHandle,
                                                                                 IN ACCESS_MASK         DesiredAccess,
                                                                                 IN POBJECT_ATTRIBUTES ObjectAttributes         OPTIONAL,
                                                                                 IN HANDLE         ProcessHandle,
                                                                                 OUT PCLIENT_ID         ClientId,
                                                                                 IN PCONTEXT         ThreadContext,
                                                                                 IN PINITIAL_TEB         UserStack,
                                                                                 IN BOOLEAN         CreateSuspended        
                                                                                 );
static TZwCreateThread ZwCreateThread = (TZwCreateThread)GetProcAddress(GetModuleHandle("ntdll.dll"),"ZwCreateThread");

typedef NTSYSAPI NTSTATUS (NTAPI *TZwResumeThread)       (       IN HANDLE         ThreadHandle,
                                                                                 OUT PULONG         SuspendCount        
                                                                                 );
static TZwResumeThread ZwResumeThread = (TZwResumeThread)GetProcAddress(GetModuleHandle("ntdll.dll"),"ZwResumeThread");

HANDLE WINAPI myCreateRemoteThread(
                                                                       HANDLE hProcess,
                                                                       LPSECURITY_ATTRIBUTES lpThreadAttributes,
                                                                       SIZE_T dwStackSize,
                                                                       LPTHREAD_START_ROUTINE lpStartAddress,
                                                                       LPVOID lpParameter,
                                                                       DWORD dwCreationFlags,
                                                                       LPDWORD lpThreadId)
{
       //by 80695073(QQ)
       //email kiss2008ufo@yahoo.com.cn
       CONTEXT       context = {CONTEXT_FULL};
       CLIENT_ID   cid={hProcess};
       DWORD       ret;
       HANDLE       hThread = NULL;
       DWORD       StackReserve;
       DWORD       StackCommit = 0x1000;
       ULONG_PTR   Stack = 0;
       INITIAL_TEB InitialTeb={};
       ULONG       x;
       const CHAR myBaseThreadInitThunk[] =
       {
               //     00830000       8BFF                       mov         edi, edi
               '\x8B','\xFF',
               //     00830002       55                           push       ebp
               '\x55',
               //     00830003       8BEC                       mov         ebp, esp
               '\x8B','\xEC',
               //     00830005       51                           push       ecx     //ntdll.RtlExitUserThread
               '\x51',
               //     00830006       53                           push       ebx     //参数
               '\x53',
               //     00830007       FFD0                       call       eax     //函数地址
               '\xFF','\xD0',
               //     00830009       59                           pop         ecx     //恢复结束函数地址
               '\x59',
               //     0083000A       50                           push       eax     //将刚才的结果压栈
               '\x50',
               //     0083000B       FFD1                       call       ecx     //调用RtlExitUserThread 结束
               '\xFF','\xD1',
               //   0083000D       90                           nop
               '\x90'
       };
       PVOID   pBaseThreadThunk = NULL; //不能释放

       //0、分配非OS的加载函数
       StackReserve = 0x1000;
       ret = ZwAllocateVirtualMemory(hProcess,
               (PVOID*)&pBaseThreadThunk,
               0,
               &StackReserve,
               MEM_COMMIT,
               PAGE_EXECUTE_READWRITE);
       if (ret >= 0x80000000)
       {
               //失败
               TRACE("Error IN myCreateRemoteThread ZwAllocateVirtualMemory0  !\n");
               goto myCreateRemoteThreadRet;
               //end
       }
       ret = ZwWriteVirtualMemory(hProcess,
               pBaseThreadThunk,
               (LPVOID)myBaseThreadInitThunk,
               sizeof(myBaseThreadInitThunk),&x);
       if (ret >= 0x80000000)
       {
               //失败
               TRACE("Error IN myCreateRemoteThread ZwAllocateVirtualMemory0  !\n");
               goto myCreateRemoteThreadRet;
               //end
       }

       //1、准备堆栈
       StackReserve = 0x10000;
       ret = ZwAllocateVirtualMemory(hProcess,
               (PVOID*)&Stack,
               0,
               &StackReserve,
               MEM_RESERVE,
               PAGE_READWRITE);
       if (ret >= 0x80000000)
       {
               //失败
               TRACE("Error IN myCreateRemoteThread ZwAllocateVirtualMemory1 !\n");
               goto myCreateRemoteThreadRet;
               //end
       }
       TRACE("OK myCreateRemoteThread:ZwAllocateVirtualMemory 0xx\n",Stack);

       InitialTeb.AllocatedStackBase = (PVOID)Stack;
       InitialTeb.StackBase = (PVOID)(Stack + StackReserve);

     
       Stack += StackReserve - StackCommit;

       Stack -= 0x1000;
       StackCommit += 0x1000;

     
       ret = ZwAllocateVirtualMemory(hProcess,
               (PVOID*)&Stack,
               0,
               &StackCommit,
               MEM_COMMIT,
               PAGE_READWRITE);
       if (ret >= 0x80000000)
       {
               //失败
               TRACE("Error IN myCreateRemoteThread ZwAllocateVirtualMemory2 !\n");
               goto myCreateRemoteThreadRet;
               //end
       }
       TRACE("OK myCreateRemoteThread:ZwAllocateVirtualMemory 2 0xx\n",Stack);
       InitialTeb.StackLimit = (PVOID)Stack;


       StackReserve = 0x1000;
       ret = ZwProtectVirtualMemory(hProcess, (PVOID*)&Stack, &StackReserve, PAGE_READWRITE | PAGE_GUARD, &x);
       if (ret >= 0x80000000)
       {
               //失败
               TRACE("Error IN myCreateRemoteThread ZwProtectVirtualMemory!\n");
               goto myCreateRemoteThreadRet;
               //end
       }
     
       InitialTeb.StackLimit = (PVOID)((ULONG_PTR)InitialTeb.StackLimit - 0x1000);
       //2、准备CONTEXT
       //   CONTEXT context = {CONTEXT_FULL};
       ret = ZwGetContextThread(GetCurrentThread(),&context);
       if (ret >= 0x80000000)
       {
               //失败
               TRACE("Error IN myCreateRemoteThread ZwGetContextThread!\n");
               goto myCreateRemoteThreadRet;
               //end
       }
       context.Esp = (DWORD)InitialTeb.StackBase;
       context.Eip = (DWORD)pBaseThreadThunk; //这里填写需要加载的地址,不过需要自己终结自己
       context.Ebx = (DWORD)lpParameter;
       //other init
       //must
       context.Eax = (DWORD)lpStartAddress;
       context.Ecx = (DWORD)GetProcAddress(GetModuleHandle("ntdll.dll"),"RtlExitUserThread");//0x778B0859;//0x77AEEC01; //ntdll.RtlExitUserThread
       context.Edx = 0x00000000; //nouse


       ret = ZwCreateThread(&hThread, THREAD_ALL_ACCESS, 0, hProcess, &cid, &context, &InitialTeb, TRUE);
       if (ret >= 0x80000000)
       {
               //失败
               TRACE("Error %d\n",GetLastError());
               goto myCreateRemoteThreadRet;
               //end
       }
       if(lpThreadId)
       {
               *lpThreadId = (DWORD)cid.UniqueThread;
       }
       if (!(dwCreationFlags & CREATE_SUSPENDED))
       {
               ZwResumeThread(hThread, NULL);
       }
myCreateRemoteThreadRet:
       return hThread;
}
 

  最后通用的使用方法是:
 
// 启动远程线程
       HANDLE hRemoteThread = NULL;
       OSVERSIONINFO svex = {sizeof(OSVERSIONINFO)};
       GetVersionEx(&svex);
       if( svex.dwMajorVersion<=5 ){
               hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnStartRoutine, lpRemoteDllName, 0, NULL);
       }else{
               hRemoteThread = myCreateRemoteThread(hProcess, NULL, 0, pfnStartRoutine, lpRemoteDllName, 0, NULL);
       }
 
 
3.通过SetWindowsHookEx安装钩子,如WH_CALLWNDPROC,WH_KEYBOARD,WH_MOUSE,WH_GETMESSAGE钩子可以实现全局注入。
SetWindowsHookEx(WH_MOUSE,(HOOKPROC)MouseProc,AfxGetInstanceHandle(),dwThreadId);
 
4.AppInit_DLLs方式:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs注入到所有加载了user32.dll的进程。
win7下会被映射到:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows,
并且需要设置LoadAppInit_DLLs为1时AppInit_DLLs才会被启用,默认为0。
例如在xp下创建一个.reg文件:
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows]
"AppInit_DLLs"="c:\\message.dll"
手动导入后是可以加载指定dll的,但是在win7下面就不行,通过该.reg文件操作的注册表子键路径并没有被重定向到
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows,
但是通过写代码的方式是成功的:
 
void LoadLibByAppInit_DLLs(LPCTSTR pszDllName,BOOL bInstall)
{
       HKEY hKey = NULL;
       DWORD dwRet = 0;

       //win7下会被映射到:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows
       if ( RegCreateKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows"), 0, 0, 0, KEY_ALL_ACCESS, 0, &hKey, 0)!=ERROR_SUCCESS ){
               return;
       }

       dwRet = RegSetValueEx(hKey,_T("LoadAppInit_DLLs"),0,REG_DWORD,(const BYTE *)&bInstall,sizeof(bInstall));
       if ( bInstall ){
               dwRet = RegSetValueEx(hKey,_T("AppInit_Dlls"),0,REG_SZ,(const BYTE *)pszDllName,lstrlen(pszDllName));
       }else{
               dwRet = RegSetValueEx(hKey,_T("AppInit_DLLs"),NULL,REG_SZ,NULL,0);
       }

       RegCloseKey(hKey);
}
 
在win7下还有一个值RequireSignedAppInit_DLLs,如果为1表示则只加载有签名的dll,默认为0表示不对dll进行验证。
参见:http://msdn.microsoft.com/en-us/library/dd744762(v=vs.85).aspx
 
5.ShellExecuteHooks方式:
local_machine\software\microsoft\windows\currentversion\Explorer\ShellExecuteHooks注入到explorer.exe进程。

6.输入法注入:http://code.google.com/p/windows-config/wiki/Win32IME

7.lsp,SPI过滤注入网络进程:http://www.vckbase.com/document/viewdoc/?id=643
  http://www.vckbase.com/document/viewdoc/?id=808
 
8.BHO。
 
9.输入表方式注入,原理就是为目标pe文件增加一个导入函数,这个导入的函数是在要注入的dll中。
这样当目标PE文件被加载时会由系统来完成它的导入库的装载工作,这样你的DLL就能被加载进去了。
可以使用类似DIYTools的PE工具来完成,代码我就不写了。
 
10.CreateProcess以挂起的方式创建目标进程,修改入口代码加载指定dll,恢复入口代码唤醒进程。
11.使用微软提供的detours库函数DetourCreateProcessWithD ll创建进程并为进程加载指定dll,这个方法原理上就是上面的CreateProcess方法,
只不过更简单更稳定了,拿来主义嘛。
12.通过DXG方式注入使用DirectX的进程,暂无资料。
13.RegisterUserApiHook
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值