【WINAPI】MessageBox细解(三)

0x00 简介

0.1 关于

MessageBox()一个看似简单的函数,其内部也有不少复杂的实现。前面的两个部分我们细节的了解了部分常用的MessageBox系列函数,并对未公开的MessageBoxTimeout()和MessageBoxWorker()进行了分析。但现阶段还是很贴近于我们一般的使用,没有具体的深入到实现原理。

第三部分我本来想一次性写完ServiceMessageBox()和SoftModalMessageBox()两个内容,但再仔细研究了SoftModalMessageBox()后我的想法被改变了,我没办法在一个篇幅内把两个函数很好的表达出来。所以第三部分我想全部交给ServiceMessageBox()。

0.2 前情提要

【WINAPI】MessageBox细解(一)
第一部分中主要讲解了三个MessageBox函数的内部实现,引出了MessageBoxWorker()和MessageBoxTimeout()并说明了如何直接使用这两个函数。

【WINAPI】MessageBox细解(二)
第二部分中主要讲解了MessageBoxWorker()和MessageBoxTimeout()两个函数的内部实现,MessageBoxTimeout()内部继续调用了MessageBoxWorker()。MessageBoxWorker()根据MB_SERVICE_NOTIFICATION,MB_DEFAULT_DESKTOP_ONLY和MB_TOPMOST三个标识,分成了两个大路,分别是ServiceMessageBox()和SoftModalMessageBox()。

0.3 工具

操作系统:Windows10。
调试器:Ollydbg以及IDA。
编译器:VS2019

0x01 程序对象

1.1 程序源码

#include <Windows.h>
#include "resource.h"

typedef int(_fastcall *MSGSERVICEPROC)(LPCWSTR,LPCWSTR,UINT,DWORD);

int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) {
	HMODULE				hDllUser32;
	MSGSERVICEPROC		ServiceMessageBox;

	if((hDllUser32 = LoadLibrary(TEXT("user32.dll"))) != NULL) {

			//这个是10.0.19041.610的user32偏移,昨天系统自动更新了,凑合着吧。
			ServiceMessageBox = (MSGSERVICEPROC)((DWORD)hDllUser32 + (DWORD)0x0007E986);
			
			ServiceMessageBox(TEXT("Hello World"), TEXT("Test"), MB_OKCANCEL | MB_HELP | MB_ICONWARNING, 5000);
	}

	return 0;
}

1.2 运行结果

ServiceMessageBox
消息盒子在锁屏界面下正常下显示,5s后自动关闭。

测试了这个程序的一些人会发现在切换锁屏的时候会反复听到MB_ICONWARNING的警报声。这是什么原因呢?
你可以把等待时间设置为30s,等待20s后在切换锁屏,重新计时你可能会豁然开朗。但这个的具体缘由我不会说明,我并不打算在这几篇文章中写到内核部分。

0x02 函数细解

2.1 预备知识

标识MB_SERVICE_NOTIFICATION在服务进程中使用的一个标识,但提到服务进程不得不考虑一个问题。那就是会话隔离

把会话隔离在这里全部讲完有些许的不现实,在此就简单的介绍一下。在一般情况下是不能在其他的会话显示窗口的,服务器管理员应该对此会非常的熟悉。举个简单的例子,就像在服务进程(会话0)中无法通过直接普通的方式使用MessageBox()弹出消息盒子。

我们在服务程序中使用下列代码:

MessageBox(NULL, TEXT("Hello World!"), TEXT("Test"), MB_YESNOCANCEL);

显然因为会话隔离这个函数并没有任何的效果。

如果使用了MB_SERVICE_NOTIFICTION呢?在服务程序编写下列代码再次测试:

MessageBox(NULL, TEXT("Hello World!"), TEXT("Test"), MB_YESNOCANCEL | MB_SERVICE_NOTIFICATION);

我们在服务进程中弹出消息盒子一般是使用WTSSendMessage()之类的函数解决。但这样会不会显得MB_SERVICE_NOTIFICATION的目的和意义不是很好的理解。
那现在我们就来详细解释一下有关于ServiceMessageBox()的原理揭开这一层迷雾。

2.2 静态分析

2.2.1 反汇编代码

贴上反汇编代码

.text:69E7E566 ; int __fastcall ServiceMessageBox(const unsigned __int16 *szCaption, const unsigned __int16 *szTitle, unsigned int dwStyle, unsigned int dwMillisecond)
.text:69E7E566 ServiceMessageBox proc near             ; CODE XREF: MessageBoxWorker(_MSGBOXDATA *)+1CB↑p
.text:69E7E566
.text:69E7E566 pUnicodeStringTitle= LSA_UNICODE_STRING ptr -40h
.text:69E7E566 pUnicodeStringText= LSA_UNICODE_STRING ptr -38h
.text:69E7E566 ReturnLength    = dword ptr -30h
.text:69E7E566 pTextStringStart= dword ptr -2Ch
.text:69E7E566 pThreadSessionId= dword ptr -28h
.text:69E7E566 UnicodeStringEnd= dword ptr -24h
.text:69E7E566 pProcessSessionId= dword ptr -20h
.text:69E7E566 TokenHandle     = dword ptr -1Ch
.text:69E7E566 Response        = dword ptr -18h
.text:69E7E566 Parameters      = dword ptr -14h
.text:69E7E566 dwCheckSum      = dword ptr -4
.text:69E7E566 dwStyle         = dword ptr  8
.text:69E7E566 dwMilliseconds  = dword ptr  0Ch
.text:69E7E566
.text:69E7E566                 mov     edi, edi
.text:69E7E568                 push    ebp
.text:69E7E569                 mov     ebp, esp
.text:69E7E56B                 sub     esp, 44h
.text:69E7E56E                 mov     eax, ___security_cookie
.text:69E7E573                 xor     eax, ebp
.text:69E7E575                 mov     [ebp+dwCheckSum], eax
.text:69E7E578                 push    ebx
.text:69E7E579                 push    esi
.text:69E7E57A                 push    edi
.text:69E7E57B                 mov     edi, ecx
.text:69E7E57D                 lea     eax, [ebp+TokenHandle]
.text:69E7E580                 xor     ecx, ecx
.text:69E7E582                 mov     ebx, edx
.text:69E7E584                 inc     ecx
.text:69E7E585                 push    eax             ; TokenHandle
.text:69E7E586                 push    ecx             ; OpenAsSelf
.text:69E7E587                 push    TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY ; DesiredAccess
.text:69E7E589                 mov     [ebp+Response], ecx
.text:69E7E58C                 call    ds:__imp__GetCurrentThread@0 ; GetCurrentThread()
.text:69E7E592                 push    eax             ; ThreadHandle
.text:69E7E593                 call    ds:__imp__NtOpenThreadToken@16 ; NtOpenThreadToken(x,x,x,x)
.text:69E7E599                 test    eax, eax
.text:69E7E59B                 js      loc_69E7E68D
.text:69E7E5A1                 lea     eax, [ebp+ReturnLength]
.text:69E7E5A4                 push    eax             ; ReturnLength
.text:69E7E5A5                 push    4               ; TokenInformationLength
.text:69E7E5A7                 lea     eax, [ebp+pThreadSessionId]
.text:69E7E5AA                 push    eax             ; TokenInformation
.text:69E7E5AB                 push    TokenSessionId  ; TokenInformationClass
.text:69E7E5AD                 push    [ebp+TokenHandle] ; TokenHandle
.text:69E7E5B0                 call    ds:__imp__NtQueryInformationToken@20 ; NtQueryInformationToken(x,x,x,x,x)
.text:69E7E5B6                 push    [ebp+TokenHandle] ; hObject
.text:69E7E5B9                 mov     esi, eax
.text:69E7E5BB                 call    ds:__imp__CloseHandle@4 ; CloseHandle(x)
.text:69E7E5C1                 test    esi, esi
.text:69E7E5C3                 js      loc_69E7E68D
.text:69E7E5C9                 lea     eax, [ebp+pProcessSessionId]
.text:69E7E5CC                 push    eax             ; pSessionId
.text:69E7E5CD                 call    ds:__imp__GetCurrentProcessId@0 ; GetCurrentProcessId()
.text:69E7E5D3                 push    eax             ; dwProcessId
.text:69E7E5D4                 call    ds:__imp__ProcessIdToSessionId@8 ; ProcessIdToSessionId(x,x)
.text:69E7E5DA                 test    eax, eax
.text:69E7E5DC                 jnz     short loc_69E7E5EF
.text:69E7E5DE                 mov     eax, large fs:30h
.text:69E7E5E4                 mov     eax, [eax+1D4h]
.text:69E7E5EA                 mov     [ebp+pProcessSessionId], eax
.text:69E7E5ED                 jmp     short loc_69E7E5F2
.text:69E7E5EF ; ---------------------------------------------------------------------------
.text:69E7E5EF
.text:69E7E5EF loc_69E7E5EF:                           ; CODE XREF: ServiceMessageBox+76↑j
.text:69E7E5EF                 mov     eax, [ebp+pProcessSessionId]
.text:69E7E5F2
.text:69E7E5F2 loc_69E7E5F2:                           ; CODE XREF: ServiceMessageBox+87↑j
.text:69E7E5F2                 cmp     [ebp+pThreadSessionId], eax
.text:69E7E5F5                 jz      loc_69E7E68D
.text:69E7E5FB                 mov     eax, offset dword_69E0954C
.text:69E7E600                 test    ebx, ebx
.text:69E7E602                 jnz     short loc_69E7E606
.text:69E7E604                 mov     ebx, eax
.text:69E7E606
.text:69E7E606 loc_69E7E606:                           ; CODE XREF: ServiceMessageBox+9C↑j
.text:69E7E606                 test    edi, edi
.text:69E7E608                 jnz     short loc_69E7E60C
.text:69E7E60A                 mov     edi, eax
.text:69E7E60C
.text:69E7E60C loc_69E7E60C:                           ; CODE XREF: ServiceMessageBox+A2↑j
.text:69E7E60C                 mov     esi, [ebp+dwMilliseconds]
.text:69E7E60F                 cmp     esi, 0FFFFFFFFh
.text:69E7E612                 jz      short loc_69E7E621
.text:69E7E614                 mov     eax, esi
.text:69E7E616                 xor     edx, edx
.text:69E7E618                 mov     ecx, 3E8h
.text:69E7E61D                 div     ecx
.text:69E7E61F                 mov     esi, eax
.text:69E7E621
.text:69E7E621 loc_69E7E621:                           ; CODE XREF: ServiceMessageBox+AC↑j
.text:69E7E621                 and     [ebp+UnicodeStringEnd], 0
.text:69E7E625                 mov     ecx, edi
.text:69E7E627                 lea     edx, [ecx+2]
.text:69E7E62A
.text:69E7E62A loc_69E7E62A:                           ; CODE XREF: ServiceMessageBox+CE↓j
.text:69E7E62A                 mov     ax, [ecx]
.text:69E7E62D                 add     ecx, 2
.text:69E7E630                 cmp     ax, word ptr [ebp+UnicodeStringEnd]
.text:69E7E634                 jnz     short loc_69E7E62A
.text:69E7E636                 sub     ecx, edx
.text:69E7E638                 mov     edx, ebx
.text:69E7E63A                 sar     ecx, 1
.text:69E7E63C                 lea     eax, [edx+2]
.text:69E7E63F                 mov     [ebp+pTextStringStart], eax
.text:69E7E642
.text:69E7E642 loc_69E7E642:                           ; CODE XREF: ServiceMessageBox+E6↓j
.text:69E7E642                 mov     ax, [edx]
.text:69E7E645                 add     edx, 2
.text:69E7E648                 cmp     ax, word ptr [ebp+UnicodeStringEnd]
.text:69E7E64C                 jnz     short loc_69E7E642
.text:69E7E64E                 sub     edx, [ebp+pTextStringStart]
.text:69E7E651                 xor     eax, eax
.text:69E7E653                 push    eax
.text:69E7E654                 lea     eax, [ebp+Response]
.text:69E7E657                 sar     edx, 1
.text:69E7E659                 push    eax
.text:69E7E65A                 push    esi
.text:69E7E65B                 push    [ebp+dwStyle]
.text:69E7E65E                 lea     eax, [ecx+ecx]
.text:69E7E661                 push    eax
.text:69E7E662                 push    edi
.text:69E7E663                 lea     eax, [edx+edx]
.text:69E7E666                 push    eax
.text:69E7E667                 push    ebx
.text:69E7E668                 push    [ebp+pThreadSessionId]
.text:69E7E66B                 xor     ebx, ebx
.text:69E7E66D                 push    ebx
.text:69E7E66E                 call    ds:__imp__WinStationSendMessageW@40 ; WinStationSendMessageW(x,x,x,x,x,x,x,x,x,x)
.text:69E7E674                 cmp     al, 1
.text:69E7E676                 jnz     short loc_69E7E689
.text:69E7E678                 mov     eax, [ebp+Response]
.text:69E7E67B                 cmp     eax, 7D00h
.text:69E7E680                 jz      short loc_69E7E689
.text:69E7E682                 cmp     eax, 7D02h
.text:69E7E687                 jnz     short loc_69E7E6EA
.text:69E7E689
.text:69E7E689 loc_69E7E689:                           ; CODE XREF: ServiceMessageBox+110↑j
.text:69E7E689                                         ; ServiceMessageBox+11A↑j
.text:69E7E689                 mov     eax, ebx
.text:69E7E68B                 jmp     short loc_69E7E6EA
.text:69E7E68D ; ---------------------------------------------------------------------------
.text:69E7E68D
.text:69E7E68D loc_69E7E68D:                           ; CODE XREF: ServiceMessageBox+35↑j
.text:69E7E68D                                         ; ServiceMessageBox+5D↑j ...
.text:69E7E68D                 push    edi             ; SourceString
.text:69E7E68E                 lea     eax, [ebp+pUnicodeStringText]
.text:69E7E691                 push    eax             ; DestinationString
.text:69E7E692                 call    ds:__imp__RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)
.text:69E7E698                 push    ebx             ; SourceString
.text:69E7E699                 lea     eax, [ebp+pUnicodeStringTitle]
.text:69E7E69C                 push    eax             ; DestinationString
.text:69E7E69D                 call    ds:__imp__RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)
.text:69E7E6A3                 lea     eax, [ebp+pUnicodeStringText]
.text:69E7E6A6                 mov     [ebp+Parameters], eax
.text:69E7E6A9                 lea     eax, [ebp+pUnicodeStringTitle]
.text:69E7E6AC                 mov     [ebp+Parameters+4], eax
.text:69E7E6AF                 mov     eax, [ebp+dwStyle]
.text:69E7E6B2                 mov     [ebp+Parameters+8], eax
.text:69E7E6B5                 mov     eax, [ebp+dwMilliseconds]
.text:69E7E6B8                 mov     [ebp+Parameters+0Ch], eax
.text:69E7E6BB                 lea     eax, [ebp+Response]
.text:69E7E6BE                 push    eax             ; Response
.text:69E7E6BF                 push    1               ; ValidResponseOptions
.text:69E7E6C1                 lea     eax, [ebp+Parameters]
.text:69E7E6C4                 push    eax             ; Parameters
.text:69E7E6C5                 push    3               ; UnicodeStringParameterMask
.text:69E7E6C7                 push    4               ; NumberOfParameters
.text:69E7E6C9                 push    50000018h       ; ErrorStatus
.text:69E7E6CE                 call    ds:__imp__NtRaiseHardError@24 ; NtRaiseHardError(x,x,x,x,x,x)
.text:69E7E6D4                 test    eax, eax
.text:69E7E6D6                 js      short loc_69E7E6E0
.text:69E7E6D8                 mov     eax, [ebp+Response]
.text:69E7E6DB                 cmp     eax, 0Bh
.text:69E7E6DE                 jb      short loc_69E7E6E3
.text:69E7E6E0
.text:69E7E6E0 loc_69E7E6E0:                           ; CODE XREF: ServiceMessageBox+170↑j
.text:69E7E6E0                 xor     eax, eax
.text:69E7E6E2                 inc     eax
.text:69E7E6E3
.text:69E7E6E3 loc_69E7E6E3:                           ; CODE XREF: ServiceMessageBox+178↑j
.text:69E7E6E3                 mov     eax, ds:dword_69E09520[eax*4]
.text:69E7E6EA
.text:69E7E6EA loc_69E7E6EA:                           ; CODE XREF: ServiceMessageBox+121↑j
.text:69E7E6EA                                         ; ServiceMessageBox+125↑j
.text:69E7E6EA                 mov     ecx, [ebp+dwCheckSum]
.text:69E7E6ED                 pop     edi
.text:69E7E6EE                 pop     esi
.text:69E7E6EF                 xor     ecx, ebp
.text:69E7E6F1                 pop     ebx
.text:69E7E6F2                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:69E7E6F7                 leave
.text:69E7E6F8                 retn    8
.text:69E7E6F8 ServiceMessageBox endp

其中部分局部变量和结构成员的命名是个人自己取得。

2.2.2 核心代码

以前两篇相同,这么长的反汇编代码一行行解释显然不合乎情理,我将择取核心代码用于解释这个函数。

  1. 核心代码A:69E7E57D~ 69E7E59B
    先使用NtOpenThreadToken()获取该线程的Token。如果获取失败则跳转执行NtRaiseHardError()。(NtOpenThreadToken()若出现错误,返回内核状态0xCxxxxxxx。且这个函数显然需要在管理员及以上权限才能正常执行)。

  2. 核心代码B:69E7E5A1~ 69E7E5F5
    先通过使用NtQueryInformationToken()获取线程的会话ID,如果获取失败则跳转执行NtRaiseHardError()。
    然后在通过ProcessIdToSessionId()获取该进程的会话ID(请注意线程和进程的区别),如果获取失败则直接通过PEB获取。
    比较进程和线程的会话ID,如果相同则跳转执行NtRaiseHardError(),如果不相同则跳转执行WinStationSendMessageW()。

  3. 核心代码C:69E7E5FB~ 69E7E68B
    对字符串进行检查,处理等待时间参数看来WinStationSendMessageW()的等待时间单位是秒,计算字符串的大小,然后通过调用WinStationSendMessageW()向线程的会话ID弹出消息盒子。检查返回值。

  4. 核心代码D:69E7E68D~69E7E6E3
    通过获取RtlInitUnicodeString()获取标题和正文的字符串的Unicode字符串结构,初始化params数组,调用NtRaiseHardError()。检查返回值。
    以上就是ServiceMessageBox()的全部核心代码。

2.2.3 核心代码问答

核心代码A的问答
  1. 疑惑:我的程序总是会返回0xC000007D错误?
    答:你是否获取了管理员权限或者在服务中运行。

  2. 疑惑:NtOpenThreadToken()怎么使用?
    答:详细可以参考OpenThreadToken(),唯一的区别在于NtOpenThreadToken()返回的是返回值是NTSTATUS。

核心代码B的问答
  1. 疑惑:NtQueryInformationToken()怎么使用?
    答:微软有文档。

  2. 疑惑:还会出现线程的会话ID与进程的会话ID不同的情况?
    答:你可能很少编写服务程序,那我就介绍一种情况吧。

if(ImpersonateSelf(SecurityImpersonation)) {
	if(OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &hToken)) {
		dwSessionId = WTSGetActiveConsoleSessionId();
		if(SetTokenInformation(hToken, TokenSessionId, &dwSessionId, sizeof(DWORD))) {
			MessageBox(NULL, TEXT("修改成功"), TEXT("来自服务进程的消息"), MB_OK | MB_SERVICE_NOTIFICATION);
		}
		CloseHandle(hToken);
	}
}

使用SetTokenInformation()修改线程Token的会话ID(注:SetTokenInformation()修改会话ID需要具有System权限)。
MessageBox()在服务进程中正常显示消息盒子。

  1. 疑惑:PEB是什么?
    答:PEB是进程环境块的简称,一般FS段寄存器在系统中指向TEB,其偏移30h就是PEB的地址。这个内容我可以足够写多一篇,请百度解决。
核心代码C的问答
  1. 疑惑:WinStationSendMessageW()怎么使用?
    答:WinStationSendMessageW()的声明如下:
typedef BOOLEAN(_stdcall *WINSTASENDMSG)(HANDLE, ULONG, PWSTR, ULONG, PWSTR, ULONG, ULONG, ULONG, PULONG, BOOLEAN);

具体可以参考,RpcWinStationSendMessage()的官方文档。

核心代码D的问答
  1. 疑惑:NtRaiseHardError()怎么使用?
    答:NtRaiseHardError()是一个使用了_stdcall调用约束的函数,同时具有6个参数。
    下面是它的定义:
NTSTATUS NtRaiseHardError(NTSTATUS ErrorStatus, 
						  ULONG NumberOfParameters, 
						  ULONG UnicodeStringParameterMask, 
						  PULONG_PTR Parameters, 
						  ULONG ValidResponseOptions, 
						  PULONG Response);

ErrorStatus:内核错误状态(不过这个好像名不副实了0x50000018是一个很典型的消息状态)。
NumberOfParameters:Parameters数组的大小。
UnicodeStringParameterMask:Parameters的类型标志。
Parameters:指向Parameters数组。
ValidResponseOptions:设置Response返回值的有效性。
Response:函数结果的返回。
我们现在仅在探讨这个函数在MessageBox()中的应用,过深的内容就不在这里具体的解释,这已经严重超纲了。

示例代码

  1. NtRaiseHardError():
if((hDllNtdll = LoadLibrary(TEXT("ntdll.dll"))) != NULL) {
		NtRaiseHardError = (NTRAISEHARD)GetProcAddress(hDllNtdll, "NtRaiseHardError");
		
		RtlInitUnicodeString = (RTLINITUNCODE)GetProcAddress(hDllNtdll, "RtlInitUnicodeString");

		RtlInitUnicodeString(&uniText, TEXT("Hello World!"));
		RtlInitUnicodeString(&uniTitle, TEXT("Test"));

		params[0] = (ULONG_PTR)&uniText;
		params[1] = (ULONG_PTR)&uniTitle;
		params[2] = (ULONG_PTR)MB_OKCANCEL;
		params[3] = (ULONG_PTR)5000;

		NtRaiseHardError(0x50000018, 4, 3, params, 1, &ulResponse);
}

在消息盒子的显示为目的的情况下NtRaiseHardError(),ErrorStatus使用的是0x50000018。在成员数量为4同时mask为3 的情况下,param数组的四个成员分别为正文指针,标题指针,按钮风格和等待时间。

  1. WinStationSendMessageW():
if((hDllWINSTA = LoadLibrary(TEXT("winsta.dll"))) != NULL) {
	WinStationSendMessageW = (WINSTASENDMSG)GetProcAddress(hDllWINSTA, "WinStationSendMessageW");

	WinStationSendMessageW(	NULL,
							WTSGetActiveConsoleSessionId(),
							(PWSTR)TEXT("Test"),
							lstrlen(TEXT("Test")) * sizeof(TCHAR),
							(PWSTR)TEXT("Hello World!"),
							lstrlen(TEXT("Hello World!")) * sizeof(TCHAR),
							MB_OKCANCEL,
							5,
							&ulResponse,
							FALSE
	);
}

因为方便操作并没有在服务程序中执行,会话ID选择了当前的活动会话。

运行结果

  1. NtRaiseHardError():
    NtRaiseHardError()
    窗口正常显示,并在10s后消失,为了方便截图,这里的时间有所延长。

  2. WinStationSendMessageW():
    WinStationSendMessageW()
    窗口正常显示,并在10s后消失,为了方便截图,这里的时间有所延长。

0x03 总结

咕咕咕,原定计划是要把SodtModalMessageBox()也包含在这一部分中的,但是出于这个函数实在是太长了,不太合适整合在一起,便把它独立了出来顺便偷个小懒。

!!刚刚开始发布文章,有很多不清楚的地方,如果有什么不对或者违反规定的地方,请第一时间联系作者哦。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值