监控USB设备插拔

最近做了UKey加密中设计到USB设备. 因UKEy是用来加密和执行PC与项目间通信加密的介质.从作用范围来讲不是传统意义上U盘作为存储介质来使用.其实熟悉网银驱动DR应该了解.在网银系统安全上一个最基本需求就是动态即时监控通信PC驱动以及设备列表通信变化.当然包括我们加密存储介质在PC上USB插拔.

思路一.在WinFrom中通过拦截Windows 消息机制来实现. 类似定义MEssageForm窗体. 假如用鼠标左击一下窗体, 系统会收到一条 WM_LBUTTONDOWN 消息;当鼠标抬起, 系统又会收到 WM_LBUTTONUP 消息.系统收到消息后, 会告诉窗体发生的事情, 然后窗体再做出反应; 当然窗体能否做出反应要看窗体是否有相应的响应代码. 同样也可以把USB设备插拔事件通过重写窗体WndProc(Ref Message m)方法拦截并处理.

首先引入命名空间:

<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   1:  </span><span style="color:#0000ff">using</span> System.Management;</span></span>
   2:  using System.Threading;
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   3:  </span><span style="color:#0000ff">using</span> System.Security.Permissions;</span></span>

定义Device Management Event的枚举:

<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   1:  </span>  <span style="color:#0000ff">public</span> <span style="color:#0000ff">enum</span> DeviceEvent : <span style="color:#0000ff">int</span></span></span>
   2:          {
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   3:  </span>            DBT_CONFIGCHANGECANCELED = 0x0019,</span></span>
   4:              DBT_CONFIGCHANGED=0x0018,
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   5:  </span>            DBT_CUSTOMEVENT=0x8006,</span></span>
   6:              DBT_DEVICEARRIVAL=0x8000,//USB Insert DEvice Statu
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   7:  </span>            DBT_DEVICEQUERYREMOVE=0x8001,</span></span>
   8:              DBT_DEVICEQUERYREMOVEFAILED=0x8002,
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   9:  </span>            DBT_DEVICEREMOVEPENDING=0x8003,<span style="color:#008000">//USB Revoing.</span></span></span>
  10:              DBT_DEVICEREMOVECOMPLETE=0x8004,//USB Remove Completed
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  11:  </span>            DBT_DEVICETYPESPECIFIC=0x8005,</span></span>
  12:              DBT_DEVNODES_CHANGED=0x0007,//Device List _Changed
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  13:  </span>            DBT_QUERYCHANGECONFIG=0x0017,</span></span>
  14:              DBT_USERDEFINED=0xFFFF
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  15:  </span>        }</span></span>

其中涉及到USB设备插拔的是DEVICEREMOVEPENDING/DEVICEREMOVECOMPLETE[删除]  DEVICEARRIVAL[插入设备]  重写WinFProc实现窗体上对Windows MEssage进行拦截并重新处理:

<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   1:  </span>      [PermissionSet(SecurityAction.Demand, Name = <span style="color:#006080">"FullTrust"</span>)]</span></span>
   2:          protected override void WndProc(ref Message m)
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   3:  </span>        {</span></span>
   4:              base.WndProc(ref m);
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   5:  </span>            DeviceEvent lEvent;</span></span>
   6:      
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   7:  </span>            lEvent = (DeviceEvent)m.WParam.ToInt32();</span></span>
   8:              switch (lEvent)
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   9:  </span>            {</span></span>
  10:                  case DeviceEvent.DBT_DEVICEARRIVAL://[Insert]
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  11:  </span>                    <span style="color:#0000ff">this</span>.CheckDeviceStatus_Lable.BackColor = Color.Green;</span></span>
  12:                      this.CheckDeviceStatus_Lable.Text = "----Connection Device!----";
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  13:  </span>                    MessageBox.Show(<span style="color:#006080">"Just Insert At Moment !"</span>, <span style="color:#006080">"Insert"</span>);</span></span>
  14:                      break;
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  15:  </span>                <span style="color:#0000ff">case</span> DeviceEvent.DBT_DEVICEREMOVECOMPLETE:<span style="color:#008000">//[REmove]</span></span></span>
  16:                      this.CheckDeviceStatus_Lable.BackColor = Color.Red;
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  17:  </span>                    <span style="color:#0000ff">this</span>.CheckDeviceStatus_Lable.Text = <span style="color:#006080">"------No Connection------"</span>;</span></span>
  18:                      MessageBox.Show("Remove Complete At Moment!", "Remove");
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  19:  </span>                    <span style="color:#0000ff">break</span>;</span></span>
  20:                  case DeviceEvent.DBT_DEVNODES_CHANGED://[Device List Have Changed]
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  21:  </span>                    MessageBox.Show(<span style="color:#006080">"Device List have been Changed!"</span>);</span></span>
  22:                      break;
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  23:  </span>                <span style="color:#0000ff">default</span>:</span></span>
  24:                      break;
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  25:  </span>            }</span></span>
  26:          }

首先USB在进行插拔即时如果没有存储介质即Disk或是无驱动的方式可能不能触发DeviceEvent.DBT_DEVICEREMOVECOMPLETE 和 DeviceEvent.DBT_DEVICEARRIVAL插把事件. 但是一点是可以确认的是.PC端只要接受USB设备.PC识别之后设备列表肯定会发生变化. 如果不是加密Key. u盘的 盘符方式存在Disk存储介质.则没有问题.

当然需要移植这种基于From窗体系统Message拦截方法时发现这种可重用性就不高.换一种思路采用Windows底层方式WMI实现.

WMI以CIMOM为基础,CIMOM即公共信息模型对象管理器[Common Information Model Object Manager],是一个描述操作系统构成单元的对象数据库,为MMC和脚本程序提供了一个访问操作系统构成单元的公共接口。有了WMI,工具软件和脚本程序访问操作系统的不同部分时不需要使用不同的API;相反,操作系统的不同部分都可以插入WMI,工具软件和WMI可以方便地读写WMI.

WMI 可以产生的系统级事件的一些更有用。 每当创建 WMI 类的新实例 ; 例如对于激发称为 __instancecreationevent 事件__instancedeletionevent 时将触发一个实例将被删除.  当然USB插拔时同样获得这个系统事件:

<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   1:  </span>  <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> ControlUSBConnectionStatu()</span></span>
   2:          {
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   3:  </span>            ManagementEventWatcher getEventWatcher = <span style="color:#0000ff">null</span>;</span></span>
   4:              WqlEventQuery getEventQuery = null;
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   5:  </span> </span></span>
   6:              ManagementOperationObserver getObserver = new ManagementOperationObserver();
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   7:  </span> </span></span>
   8:              //Bind to Loacl Machine and Watch the PortConnection
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   9:  </span>            ManagementScope getScope = <span style="color:#0000ff">new</span> ManagementScope(<span style="color:#006080">"root\\CIMV2"</span>);</span></span>
  10:              getScope.Options.EnablePrivileges = true;//set requeired
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  11:  </span> </span></span>
  12:              try
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  13:  </span>            {</span></span>
  14:                  getEventQuery = new WqlEventQuery();
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  15:  </span>                getEventQuery.EventClassName = <span style="color:#006080">"__InstanceOperationEvent"</span>;</span></span>
  16:                  getEventQuery.WithinInterval = new TimeSpan(0, 0, 0, 1);
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  17:  </span>                getEventQuery.Condition = <span style="color:#006080">@"TargetInstance ISA 'Win32_DiskDrive' "</span>;</span></span>
<span style="color:#000000"><span style="color:#000000"><span style="color:#008000">                       //[Disk must have DiskDrive fuck ]</span></span></span>
 
  20:                  //Event Watcher [Test Event and semd informatio to this message and create new informtion .]
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  21:  </span>                getEventWatcher = <span style="color:#0000ff">new</span> ManagementEventWatcher(getEventQuery);</span></span>
  22:                  getEventWatcher.EventArrived += new EventArrivedEventHandler(getEventWatcher_EventArrived);
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  23:  </span>                getEventWatcher.Start();<span style="color:#008000">//Start Watch Event</span></span></span>
 
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  25:  </span>            }</span></span>
  26:              catch (Exception se)
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  27:  </span>            { }</span></span>
  28:              finally
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  29:  </span>            {</span></span>
  30:                  // getEventWatcher.Stop();
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  31:  </span>            }</span></span>
  32:          }

当发生USB插拔并成功监听到事件时处理方法:

<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   1:  </span> <span style="color:#0000ff">void</span> getEventWatcher_EventArrived(<span style="color:#0000ff">object</span> sender, EventArrivedEventArgs e)</span></span>
   2:          {
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   3:  </span>            ManagementBaseObject getBaseObject = (ManagementBaseObject)e.NewEvent;</span></span>
   4:              if ((getBaseObject.ClassPath.ClassName == "__InstanceCreationEvent"))
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   5:  </span>            {</span></span>
   6:                  //Usb Inserted
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   7:  </span>                MessageBox.Show(<span style="color:#006080">"USB Disk Inserted!"</span>);</span></span>
   8:              }
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   9:  </span>            <span style="color:#0000ff">else</span></span></span>
  10:              {
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  11:  </span>                <span style="color:#008000">//Usb Removed</span></span></span>
  12:                  MessageBox.Show("USB Device Removed!");
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  13:  </span>            }</span></span>
  14:          }

如上在定义时设置一个Condition[条件]:存在Disk Driver 磁盘驱动.如果类似某些UKey不存在磁盘驱动同时也无存储介质 经过测试你会发现.加密的UKey插入 如上的WMI监听事件并不能扑捉到USB插拔. 但是针对这种方式我们还有一种更为彻底的方式就是USB插拔唯一可以确定在PC识别必然变化的因素是系统设备列表发生更新.

WMI处理监听:

<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   1:  </span>       <span style="color:#008000">/// <summary></span></span></span>
   2:          /// 监听USB Device设备插拔事件 完整操作.
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   3:  </span>        <span style="color:#008000">/// WMI Handle Event Change Device List chenkai</span></span></span>
   4:          /// </summary>
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   5:  </span>        <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> RegisterDeviceWMIEventStatu()</span></span>
   6:          {
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   7:  </span>            <span style="color:#0000ff">try</span> </span></span>
   8:              {
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   9:  </span>                <span style="color:#008000">//Device List HAve Changed  And Send Message </span></span></span>
  10:                  WqlEventQuery query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent");
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  11:  </span>                ManagementEventWatcher watcher = <span style="color:#0000ff">new</span> ManagementEventWatcher(query);</span></span>
  12:                  watcher.EventArrived += new EventArrivedEventHandler(watcher_EventArrived);
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  13:  </span>                watcher.Start();  <span style="color:#008000">// Start listening for events</span></span></span>
  14:              }
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  15:  </span>            <span style="color:#0000ff">catch</span> (Exception se)</span></span>
  16:              { }
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  17:  </span>        }</span></span>

WMI监听处理函数:

<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   1:  </span> <span style="color:#0000ff">void</span> watcher_EventArrived(<span style="color:#0000ff">object</span> sender, EventArrivedEventArgs e)</span></span>
   2:          {
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   3:  </span>            <span style="color:#0000ff">string</span> geteventtype = e.NewEvent.GetPropertyValue(<span style="color:#006080">"EventType"</span>).ToString();</span></span>
   4:              ManagementBaseObject getEventObject = (ManagementBaseObject)e.NewEvent;
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   5:  </span> </span></span>
   6:              if (getEventObject != null)
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   7:  </span>            {</span></span>
   8:                  //close Operator 
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   9:  </span>                <span style="color:#0000ff">this</span>.CloseDeviceEqument();</span></span>
  10:              }
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  11:  </span>        }</span></span>

其实这依然还是一种折中办法.因为导致PC端设备列表发生变化的因素有很多.如何判定是USB口发生设备变化.WMI依然没有让我们失望:

<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   1:  </span>  <span style="color:#008000">//WMI Control The USB Device Change Statu And Send Message to When IT‘s Changed/</span></span></span>
   2:          public void ControlUSBDeviceSTatu()
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   3:  </span>        {</span></span>
   4:              try
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   5:  </span>            {</span></span>
   6:                  WqlEventQuery query = new WqlEventQuery("select * from Win32_VolumeChangeEvent");
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   7:  </span>                ManagementEventWatcher getwatcher = <span style="color:#0000ff">new</span> ManagementEventWatcher(query);</span></span>
   8:                  getwatcher.EventArrived += new EventArrivedEventHandler(getwatcher_EventArrived);
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   9:  </span>                getwatcher.Start();</span></span>
  10:              }
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  11:  </span>            <span style="color:#0000ff">catch</span> (Exception se)</span></span>
  12:              { }
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  13:  </span>        }</span></span>

WMI在USB事情处理函数:

   2:          void getwatcher_EventArrived(object sender, EventArrivedEventArgs e)
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   3:  </span>        {</span></span>
   4:              MessageBox.Show(e.NewEvent.GetText(TextFormat.Mof).ToString());
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   5:  </span>        }</span></span>

这样来一来就可以清晰判定USB在插拔时所发生在系统PC端的变化通知给应用程序来进行处理.如何获得当前PC端 USB Driver列表.经过几番测试找到一种很好的方式获取全部的USB Driver信息:

<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   1:  </span> <span style="color:#008000">//Get ALL USB DRiver And Driver Property Fuck this shit。chenkai</span></span></span>
   2:          public static string[] AllInformation()
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   3:  </span>        {</span></span>
   4:              StringCollection propNames = new StringCollection();
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   5:  </span>            ManagementClass driveClass = <span style="color:#0000ff">new</span> ManagementClass(<span style="color:#006080">"Win32_USBController"</span>);</span></span>
   6:              PropertyDataCollection props = driveClass.Properties;
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">   7:  </span>            <span style="color:#0000ff">foreach</span> (PropertyData driveProperty <span style="color:#0000ff">in</span> props)</span></span>
   8:                  propNames.Add(driveProperty.Name);
 
  12:              int idx = 0;
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  13:  </span>            ManagementObjectCollection drives = driveClass.GetInstances();</span></span>
  14:              string _s = string.Empty;
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  15:  </span>            List<<span style="color:#0000ff">string</span>> harddisk = <span style="color:#0000ff">new</span> List<<span style="color:#0000ff">string</span>>();</span></span>
  16:   
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  17:  </span>            <span style="color:#0000ff">foreach</span> (ManagementObject drv <span style="color:#0000ff">in</span> drives)</span></span>
  18:              {
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  19:  </span>                idx++;</span></span>
  20:                  _s = string.Format(" USB Driver({0}) Properties ", idx);
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  21:  </span>                harddisk.Add(_s);</span></span>
  22:                  foreach (string strProp in propNames)
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  23:  </span>                {</span></span>
  24:                      _s = string.Format("Property: {0}, Value: {1}", strProp, drv[strProp]);
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  25:  </span>                    harddisk.Add(_s);</span></span>
  26:                  }
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  27:  </span>            }</span></span>
  28:              string[] _ss = harddisk.ToArray();
<span style="color:#000000"><span style="color:#000000"><span style="color:#606060">  29:  </span>            <span style="color:#0000ff">return</span> _ss;</span></span>
  30:          }

WMI的更多体现是对Windows 交互中进一步封装和管理.

Can考资料:

Device ManageMent Events

System.Form.control.WinProc[]Method

System.Form.Control.Message[]Method

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要在Windows上监控USB设备插拔动作,可以使用Windows API函数来实现。以下是一个使用C语言编写的示例代码: ```c #include <Windows.h> #include <Dbt.h> #include <stdio.h> LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_DEVICECHANGE: { PDEV_BROADCAST_HDR pBroadcastHdr = (PDEV_BROADCAST_HDR)lParam; if (pBroadcastHdr != NULL) { switch (wParam) { case DBT_DEVICEARRIVAL: { PDEV_BROADCAST_DEVICEINTERFACE pBroadcastDevInterface = (PDEV_BROADCAST_DEVICEINTERFACE)pBroadcastHdr; if (pBroadcastDevInterface->dbcc_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { printf("USB device inserted\n"); // 在这里处理USB设备插入后的操作 } break; } case DBT_DEVICEREMOVECOMPLETE: { PDEV_BROADCAST_DEVICEINTERFACE pBroadcastDevInterface = (PDEV_BROADCAST_DEVICEINTERFACE)pBroadcastHdr; if (pBroadcastDevInterface->dbcc_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { printf("USB device removed\n"); // 在这里处理USB设备拨出后的操作 } break; } } } break; } default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } int main() { WNDCLASS wc = {0}; wc.lpfnWndProc = WndProc; wc.hInstance = GetModuleHandle(NULL); wc.lpszClassName = "USBDeviceMonitor"; if (!RegisterClass(&wc)) { printf("Failed to register window class\n"); return 1; } HWND hWnd = CreateWindowEx(0, wc.lpszClassName, "", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); if (hWnd == NULL) { printf("Failed to create window\n"); return 1; } DEV_BROADCAST_DEVICEINTERFACE notificationFilter = {0}; notificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); notificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; notificationFilter.dbcc_classguid = GUID_DEVINTERFACE_USB; HDEVNOTIFY hDevNotify = RegisterDeviceNotification(hWnd, &notificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE); if (hDevNotify == NULL) { printf("Failed to register device notification\n"); return 1; } MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } UnregisterDeviceNotification(hDevNotify); DestroyWindow(hWnd); return 0; } ``` 在上面的代码中,我们创建了一个窗口,并在窗口的回调函数`WndProc`中处理`WM_DEVICECHANGE`消息,该消息会在设备插拔时触发。通过检查`wParam`参数和设备类型,我们可以确定是设备插入还是拨出,并在相应的分支中执行相应的操作。 请注意,该示例代码是一个简化的示例,只演示了如何监控USB设备插拔动作。在实际应用中,您可能需要根据需要进行更多的处理,例如打开和关闭设备、读取设备信息等等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一口Linux

众筹植发

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值