深入挖掘Windows指令碼技術(轉)

PigBaby2007發表於2007-08-08

  前言

  本文講述一些Windows指令碼程式設計的知識和技巧。這裡的Windows指令碼是指"Windows Script Host"(WSH Windows指令碼宿主),而不是HTML或ASP中的指令碼。前者由Wscript或Cscript解釋,後兩者分別由IE和IIS負責解釋。描述的語言是VBScript。本文假設讀者有一定的Windows指令碼程式設計的基礎。如果你對此還不瞭解,請先學習《Windows指令碼技術》[1]。[@more@]

  

  回顧WSH物件

  得益於com技術的支援,WSH能提供比批處理(.bat)更強大的功能。說白了,wsh不過是呼叫現成的“控制元件”作為一個物件,用物件的屬性和方法實現目的。

  

  常用的物件有:

  WScript

  Windows指令碼宿主物件模型的根物件,要使用WSH自然離不開它。它提供多個子物件,比如WScript.Arguments和WScript.Shell。前者提供對整個命令列引數集的訪問,後者可以執行程式、操縱登錄檔內容、建立快捷方式或訪問系統資料夾。

  

  Scripting.FileSystemObject

  主要為IIS設計的物件,訪問檔案系統。這個恐怕是大家遇到最多的物件了,因為幾乎所有的Windows指令碼病毒都要透過它複製自己感染別人。

  

  ADODB.Stream

  ActiveX Data Objects資料庫的子物件,提供流方式訪問檔案的功能。這雖然屬於資料庫的一部分,但感謝微軟,ADO是系統自帶的。

  

  Microsoft.XMLHTTP

  為支援XML而設計的物件,透過http協議訪問網路。常用於跨站指令碼執行漏洞和SQL injection。

  

  還有很多不常見的:

  活動目錄服務介面(ADSI)相關物件 ―― 功能涉及範圍很廣,主要用於Windows域管理。

  InternetExplorer物件 ―― 做IE能做的各種事。

  Word,Excel,Outlook物件 ―― 用來處理word文件,excel表單和郵件。

  WBEM物件 ―― WBEM即Web-Based Enterprise Management。它為管理Windows提供強大的功能支援。下一節提到的WMI服務提供該物件的介面。

  

  很顯然,WSH可以利用的物件遠遠不止這些。本文掛一漏萬,談一些較實用的物件及其用法。

  先看一個支援斷點續傳下載web資源的例子,它用到了上面說的4個常用物件。

  

  if (lcase(right(wscript.fullname,11))="wscript.exe") then '判斷指令碼宿主的名稱'

  die("Script host must be CScript.exe.") '指令碼宿主不是CScript,於是就die了'

  end if

  

  if wscript.arguments.count<1 then '至少要有一個引數'

  die("Usage: cscript webdl.vbs url [filename]") '麻雀雖小五臟俱全,Usage不能忘'

  end if

  

  url=wscript.arguments(0) '引數陣列下標從0開始'

  if url="" then die("URL can't be null.") '敢唬我,空url可不行'

  if wscript.arguments.count>1 then '先判斷引數個數是否大於1'

  filename=wscript.arguments(1) '再訪問第二個引數'

  else '如果沒有給出檔名,就從url中獲得'

  t=instrrev(url,"/") '獲得最後一個"/"的位置'

  if t=0 or t=len(url) then die("Can not get filename to save.") '沒有"/"或以"/"結尾'

  filename=right(url,len(url)-t) '獲得要儲存的檔名'

  end if

  if not left(url,7)=" then url="&url '如果粗心把“http://”忘了,加上'

  

  set fso=wscript.createobject("Scripting.FileSystemObject") 'FSO,ASO,HTTP三個物件一個都不能少'

  set aso=wscript.createobject("ADODB.Stream")

  set http=wscript.createobject("Microsoft.XMLHTTP")

  

  if fso.fileexists(filename) then '判斷要下載的檔案是否已經存在'

  start=fso.getfile(filename).size '存在,以當前檔案大小作為開始位置'

  else

  start=0 '不存在,一切從零開始'

  fso.createtextfile(filename).close '新建檔案'

  end if

  

  wscript.stdout.write "Connectting..." '好戲剛剛開始'

  current=start '當前位置即開始位置'

  do

  http.open "GET",url,true '這裡用非同步方式呼叫HTTP'

  http.setrequestheader "Range","bytes="&start&"-"&cstr(start+20480) '斷點續傳的奧秘就在這裡'

  http.setrequestheader "Content-Type:","application/octet-stream"

  http.send '構造完資料包就開始傳送'

  

  for i=1 to 120 '迴圈等待'

  if http.readystate=3 then showplan() '狀態3表示開始接收資料,顯示進度'

  if http.readystate=4 then exit for '狀態4表示資料接受完成'

  wscript.sleep 500 '等待500ms'

  next

  if not http.readystate=4 then die("Timeout.") '1分鐘還沒下完20k?超時!'

  if http.status>299 then die("Error: "&http.status&" "&http.statustext) '不是吧,又出錯?'

  if not http.status=206 then die("Server Not Support Partial Content.") '伺服器不支援斷點續傳'

  

  aso.type=1 '資料流型別設為位元組'

  aso.open

  aso.loadfromfile filename '開啟檔案'

  aso.position=start '設定檔案指標初始位置'

  aso.write http.responsebody '寫入資料'

  aso.savetofile filename,2 '覆蓋儲存'

  aso.close

  

  range=http.getresponseheader("Content-Range") '獲得http頭中的"Content-Range"'

  if range="" then die("Can not get range.") '沒有它就不知道下載完了沒有'

  temp=mid(range,instr(range,"-")+1) 'Content-Range是類似123-456/789的樣子'

  current=clng(left(temp,instr(temp,"/")-1)) '123是開始位置,456是結束位置'

  total=clng(mid(temp,instr(temp,"/")+1)) '789是檔案總位元組數'

  if total-current=1 then exit do '結束位置比總大小少1就表示傳輸完成了'

  start=start+20480 '否則再下載20k'

  loop while true

  

  wscript.echo chr(13)&"Download ("&total&") Done." '下載完了,顯示總位元組數'

  

  function die(msg) '函式名來自Perl內建函式die'

  wscript.echo msg '交代遺言^_^'

  wscript.quit '去見馬克思了'

  end function

  

  function showplan() '顯示下載進度'

  if i mod 3 = 0 then c="/" '簡單的動態效果'

  if i mod 3 = 1 then c="-"

  if i mod 3 = 2 then c=""

  wscript.stdout.write chr(13)&"Download ("¤t&") "&c&chr(8)'13號ASCII碼是回到行首,8號是退格'

  end function

  

  可以看到,http控制元件的功能是很強大的。透過對http頭的操作,很容易就實現斷點續傳。例子中只是單執行緒的,事實上由於http控制元件支援非同步呼叫和事件,也可以實現多執行緒下載。在MSDN裡有詳細的用法。至於斷點續傳的詳細資料,請看RFC2616。

  

  FSO和ASO都可以訪問檔案,他們有什麼區別呢?其實,ASO除了在訪問位元組(非文字)資料有用外,就沒有存在的必要了。如果想把例子中的ASO用FSO來實現,那麼寫入http.responsebody的時候會出錯。反之也不行,ASO無法判斷檔案是否存在。如果檔案不存在,loadfromfile就直接出錯,沒有改正的機會。當然,可以用on error resume next語句讓指令碼宿主忽略非致命錯誤,自己捕捉並處理。但有現成的fileexists()為什麼不用呢?

  

  另外,由於FSO經常被指令碼病毒和ASP木馬利用,所以管理員可能會在登錄檔中修改該控制元件的資訊,使指令碼無法建立FSO。其實執行一個命令regsvr32 /s scrrun.dll就恢復了。即使scrrun.dll被刪除,自己複製一個過去就行。

  

  熱身完之後,下面我們來看一個功能強大的物件――WBEM(由WMI提供)。

  

  WMI服務

  先看看MSDN裡是怎麼描述WMI的――Windows 管理規範 (WMI) 是可伸縮的系統管理結構,它採用一個統一的、基於標準的、可擴充套件的物件導向介面。我在剛開始理解WMI的時候,總以為WMI是"Windows管理介面"(Interface),呵呵。

  

  再看什麼是WMI服務――提供共同的介面和物件模式以便訪問有關作業系統、裝置、應用程式和服務的管理資訊。如果此服務被終止,多數基於Windows的軟體將無法正常執行。如果此服務被禁用,任何依賴它的服務將無法啟動。

  

  看上去似乎是個很重要的服務。不過,預設情況下並沒有服務依賴它,反而是它要依賴RPC和EventLog服務。但它又是時常用到的。我把WMI服務設定為手動啟動並停止,使用電腦一段時間,發現WMI服務又啟動了。被需要就啟動,這是服務設定為“手動”的特點。當我知道WMI提供的管理資訊有多龐大後,對WMI服務的自啟動就不感到奇怪了。

  

  想直觀瞭解WMI的複雜,可以使用WMITools.exe[2]這個工具。這是一個工具集。使用其中的WMI Object Browser可以看到很多WMI提供的物件,其複雜程度不亞於登錄檔。更重要的是,WMI還提供動態資訊,比如當前程式、服務、使用者等。

  

  WMI的邏輯結構是這樣的:

  首先是WMI使用者,比如指令碼(確切的說是指令碼宿主)和其他用到WMI介面的應用程式。由WMI使用者訪問CIM物件管理器WinMgmt(即WMI服務),後者再訪問CIM(公共資訊模型Common Information Model)儲存庫。靜態或動態的資訊(物件的屬性)就儲存在CIM庫中,同時還存有物件的方法。一些操作,比如啟動一個服務,透過執行物件的方法實現。這實際上是透過COM技術呼叫了各種dll。最後由dll中封裝的API完成請求。

  

  WMI是事件驅動的,作業系統、服務、應用程式、裝置驅動程式等都可作為事件源,透過COM介面生成事件通知。WinMgmt捕捉到事件,然後重新整理CIM庫中的動態資訊。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10144097/viewspace-934726/,如需轉載,請註明出處,否則將追究法律責任。

相關文章