整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          淺談進程隱藏技術

          在之前幾篇文章已經學習了解了幾種鉤取的方法

          ● 淺談調試模式鉤取

          ● 淺談熱補丁

          ● 淺談內聯鉤取原理與實現

          ● 導入地址表鉤取技術

          這篇文章就利用鉤取方式完成進程隱藏的效果。

          進程遍歷方法

          在實現進程隱藏時,首先需要明確遍歷進程的方法。

          CreateToolhelp32Snapshot

          CreateToolhelp32Snapshot函數用于創建進程的鏡像,當第二個參數為0時則是創建所有進程的鏡像,那么就可以達到遍歷所有進程的效果。

          #include <iostream>
          #include <Windows.h>
          #include <TlHelp32.h>
          
          int main()
          {
              //設置編碼,便于后面能夠輸出中文
              setlocale(LC_ALL, "zh_CN.UTF-8");
              //創建進程鏡像,參數0代表創建所有進程的鏡像
              HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
              if (hSnapshot == INVALID_HANDLE_VALUE)
              {
                  std::cout << "Create Error" << std::endl;
                  exit(-1);
              }
          
              /*
              * typedef struct tagPROCESSENTRY32 { 
              * DWORD dwSize;               進程信息結構體大小,首次調用之前必須初始化
              * DWORD cntUsage;              引用進程的次數,引用次數為0時,則進程結束
              * DWORD th32ProcessID;           進程的ID
              * ULONG_PTR th32DefaultHeapID;       進程默認堆的標識符,除工具使用對我們沒用
              * DWORD th32ModuleID;                  進程模塊的標識符
              * DWORD cntThreads;             進程啟動的執行線程數
              * DWORD th32ParentProcessID;           父進程ID
              * LONG  pcPriClassBase;          進程線程的基本優先級
              * DWORD dwFlags;              保留
              * TCHAR szExeFile[MAX_PATH];          進程的路徑
              * } PROCESSENTRY32; 
              * typedef PROCESSENTRY32 *PPROCESSENTRY32; 
              */
              PROCESSENTRY32 pi;
              pi.dwSize = sizeof(PROCESSENTRY32);
              //取出第一個進程
              BOOL bRet = Process32First(hSnapshot, &pi);
              while (bRet)
              {
                  wprintf(L"進程路徑:%s\t進程號:%d\n", pi.szExeFile, pi.th32ProcessID);
                  //取出下一個進程
                  bRet = Process32Next(hSnapshot, &pi);
              }
          }
          

          EnumProcesses

          EnumProcesses用于將所有進程號的收集。

          #include <iostream>
          #include <Windows.h>
          #include <Psapi.h>
          
          int main()
          {
              setlocale(LC_ALL, "zh_CN.UTF-8");
          
              DWORD processes[1024], dwResult, size;
              unsigned int i;
              //收集所有進程的進程號
              if (!EnumProcesses(processes, sizeof(processes), &dwResult))
              {
                  std::cout << "Enum Error" << std::endl;
              }
              
              //進程數量
              size = dwResult / sizeof(DWORD);
          
              for (i = 0; i < size; i++)
              {
                  //判斷進程號是否為0
                  if (processes[i] != 0)
                  {
                      //用于存儲進程路徑
                      TCHAR szProcessName[MAX_PATH] = { 0 };
                      //使用查詢權限打開進程
                      HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
                          PROCESS_VM_READ,
                          FALSE,
                          processes[i]);
          
                      if (hProcess != NULL)
                      {
                          HMODULE hMod;
                          DWORD dwNeeded;
                          //收集該進程的所有模塊句柄,第一個句柄則為文件路徑
                          if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),
                              &dwNeeded))
                          {
                              //根據句柄獲取文件路徑
                              GetModuleBaseName(hProcess, hMod, szProcessName,
                                  sizeof(szProcessName) / sizeof(TCHAR));
                          }
                          wprintf(L"進程路徑:%s\t進程號:%d\n", szProcessName, processes[i]);
                      }
                  }   
              }
          }

          ZwQuerySystemInfomation

          ZwQuerySystemInfomation函數是CreateToolhelp32Snapshot函數與EnumProcesses函數底層調用的函數,也用于遍歷進程信息。代碼參考https://cloud.tencent.com/developer/article/1454933

          #include <iostream>
          #include <Windows.h>
          #include <ntstatus.h>
          #include <winternl.h> 
          #pragma comment(lib, "ntdll.lib") 
          
          //定義函數指針
          typedef NTSTATUS(WINAPI* NTQUERYSYSTEMINFORMATION)(
              IN      SYSTEM_INFORMATION_CLASS SystemInformationClass,
              IN OUT   PVOID                    SystemInformation,
              IN      ULONG                    SystemInformationLength,
              OUT PULONG                   ReturnLength
              );
          
          int main()
          {
              //設置編碼
              setlocale(LC_ALL, "zh_CN.UTF-8");
              //獲取模塊地址
              HINSTANCE ntdll_dll = GetModuleHandle(L"ntdll.dll");
              if (ntdll_dll == NULL) {
                  std::cout << "Get Module Error" << std::endl;
                  exit(-1);
              }
          
              NTQUERYSYSTEMINFORMATION ZwQuerySystemInformation = NULL;
              //獲取函數地址
              ZwQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(ntdll_dll, "ZwQuerySystemInformation");
              if (ZwQuerySystemInformation != NULL)
              {
                  SYSTEM_BASIC_INFORMATION sbi = { 0 };
                  //查詢系統基本信息
                  NTSTATUS status = ZwQuerySystemInformation(SystemBasicInformation, (PVOID)&sbi, sizeof(sbi), NULL);
                  if (status == STATUS_SUCCESS)
                  {
                      wprintf(L"處理器個數:%d\r\n", sbi.NumberOfProcessors);
                  }
                  else
                  {
                      wprintf(L"ZwQuerySystemInfomation Error\n");
                  }
          
                  DWORD dwNeedSize = 0;
                  BYTE* pBuffer = NULL;
          
                  wprintf(L"\t----所有進程信息----\t\n");
                  PSYSTEM_PROCESS_INFORMATION psp = NULL;
                  //查詢進程數量
                  status = ZwQuerySystemInformation(SystemProcessInformation, NULL, 0, &dwNeedSize);
                  if (status == STATUS_INFO_LENGTH_MISMATCH)
                  {
                      pBuffer = new BYTE[dwNeedSize];
                      //查詢進程信息
                      status = ZwQuerySystemInformation(SystemProcessInformation, (PVOID)pBuffer, dwNeedSize, NULL);
                      if (status == STATUS_SUCCESS)
                      {
                          psp = (PSYSTEM_PROCESS_INFORMATION)pBuffer;
                          wprintf(L"\tPID\t線程數\t工作集大小\t進程名\n");
                          do {
                              //獲取進程號
                              wprintf(L"\t%d", psp->UniqueProcessId);
                              //獲取線程數量
                              wprintf(L"\t%d", psp->NumberOfThreads);
                              //獲取工作集大小
                              wprintf(L"\t%d", psp->WorkingSetSize / 1024);
                              //獲取路徑
                              wprintf(L"\t%s\n", psp->ImageName.Buffer);
                              //移動
                              psp = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)psp + psp->NextEntryOffset);
                          } while (psp->NextEntryOffset != 0);
                          delete[]pBuffer;
                          pBuffer = NULL;
                      }
                      else if (status == STATUS_UNSUCCESSFUL) {
                          wprintf(L"\n STATUS_UNSUCCESSFUL");
                      }
                      else if (status == STATUS_NOT_IMPLEMENTED) {
                          wprintf(L"\n STATUS_NOT_IMPLEMENTED");
                      }
                      else if (status == STATUS_INVALID_INFO_CLASS) {
                          wprintf(L"\n STATUS_INVALID_INFO_CLASS");
                      }
                      else if (status == STATUS_INFO_LENGTH_MISMATCH) {
                          wprintf(L"\n STATUS_INFO_LENGTH_MISMATCH");
                      }
                  }
              }
          }

          進程隱藏

          通過上述分析可以知道遍歷進程的方式有三種,分別是利用CreateToolhelp32SnapshotEnumProcesses以及ZwQuerySystemInfomation函數

          但是CreateToolhelp32SnapshotEnumProcesses函數底層都是調用了ZwQuerySystemInfomation函數,因此我們只需要鉤取該函數即可。

          由于測試環境是Win11,因此需要判斷在Win11情況下底層是否還是調用了ZwQuerySystemInfomation函數。

          可以看到在Win11下還是會調用ZwQuerySystemInfomation函數,在用戶態下該函數的名稱為NtQuerySystemInformation函數。

          這里采用內聯鉤取的方式對ZwQuerySystemInfomation進行鉤取處理,具體怎么鉤取在淺談內聯鉤取原理與實現已經介紹過了,這里就不詳細說明了。這里對自定義的ZwQuerySystemInfomation函數進行說明。

          首先第一步需要進行脫鉤處理,因為后續需要用到初始的ZwQuerySystemInfomation函數,緊接著獲取待鉤取函數的地址即可。

          ...
              //脫鉤
              UnHook("ntdll.dll", "ZwQuerySystemInformation", g_pOrgBytes);
              HMODULE hModule = GetModuleHandleA("ntdll.dll");
              //獲取待鉤取函數的地址
              PROC    pfnOld = GetProcAddress(hModule, "ZwQuerySystemInformation");
              //調用原始的ZwQuerySystemInfomation函數
              NTSTATUS status = ((NTQUERYSYSTEMINFORMATION)pfnOld)(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
          ...

          為了隱藏指定進程,我們需要遍歷進程信息,找到目標進程并且刪除該進程信息實現隱藏的效果。這里需要知道的是進程信息都存儲在SYSTEM_PROCESS_INFORMATION結構體中,該結構體是通過單鏈表對進程信息進行鏈接。因此我們通過匹配進程名稱找到對應的SYSTEM_PROCESS_INFORMATION結構體,然后進行刪除即可,效果如下圖。

          通過單鏈表中刪除節點的操作,取出目標進程的結構體。代碼如下

          ...
                  pCur = (PSYSTEM_PROCESS_INFORMATION)(SystemInformation);
                  while (true)
                  {
                      if (!lstrcmpi(pCur->ImageName.Buffer, L"test.exe"))
                      {
                          //需要隱藏的進程是最后一個節點
                          if (pCur->NextEntryOffset == 0)
                              pPrev->NextEntryOffset = 0;
                          //不是最后一個節點,則將該節點取出
                          else
                              pPrev->NextEntryOffset += pCur->NextEntryOffset;
          
                      }
                      //不是需要隱藏的節點,則繼續遍歷
                      else
                          pPrev = pCur;
                      //鏈表遍歷完畢
                      if (pCur->NextEntryOffset == 0)
                          break;
                      pCur = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)pCur + pCur->NextEntryOffset);
                  }
          ...

          完整代碼:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/inlineHook.c

          但是采用內聯鉤取的方法去鉤取任務管理器就會出現一個問題,這里將斷點取消,利用內聯鉤取的方式去隱藏進程。

          首先利用bl命令查看斷點

          緊著利用 bc [ID]刪除斷點

          在注入之后任務管理器會在拷貝的時候發生異常

          在經過一番調試后發現,由于多線程共同執行導致原本需要可寫權限的段被修改為只讀權限

          windbg可以用使用!vprot + address查看指定地址的權限,可以看到由于程序往只讀權限的地址進行拷貝處理,所以導致了異常。

          但是在執行拷貝階段是先修改了該地址為可寫權限,那么導致該原因的情況就是其他線程執行了權限恢復后切換到該線程中進行寫,所以導致了這個問題。

          因此內聯鉤取是存在多線程安全的問題,此時可以使用微軟自己構建的鉤取庫Detours,可以在鉤取過程中確保線程安全。

          【----幫助網安學習,需要網安學習資料關注我,私信回復“資料”免費獲取----】
          ① 網安學習成長路徑思維導圖
          ② 60+網安經典常用工具包
          ③ 100+SRC漏洞分析報告
          ④ 150+網安攻防實戰技術電子書
          ⑤ 最權威CISSP 認證考試指南+題庫
          ⑥ 超1800頁CTF實戰技巧手冊
          ⑦ 最新網安大廠面試題合集(含答案)
          ⑧ APP客戶端安全檢測指南(安卓+IOS)

          Detours

          項目地址:https://github.com/microsoft/Detours

          環境配置

          參考:https://www.cnblogs.com/linxmouse/p/14168712.html

          使用vcpkg下載

          vcpkg.exe install detours:x86-windows
          vcpkg.exe install detours:x64-windows
          vcpkg.exe integrate install

          實例

          掛鉤

          利用Detours掛鉤非常簡單,只需要根據下列順序,并且將自定義函數的地址與被掛鉤的地址即可完成掛鉤處理。

          ...
                  //用于確保在 DLL 注入或加載時,恢復被 Detours 修改的進程鏡像,保持穩定性
                  DetourRestoreAfterWith();
                  //開始一個新的事務來附加或分離
                  DetourTransactionBegin();
                  //進行線程上下文的更新
                  DetourUpdateThread(GetCurrentThread());
                  //掛鉤
                  DetourAttach(&(PVOID&)TrueZwQuerySystemInformation, ZwQuerySystemInformationEx);
                  //提交事務
                  error = DetourTransactionCommit();
          ...

          脫鉤

          然后根據順序完成脫鉤即可。

          ...
                  //開始一個新的事務來附加或分離
                  DetourTransactionBegin();
                  //進行線程上下文的更新
                  DetourUpdateThread(GetCurrentThread());
                  //脫鉤
                  DetourDetach(&(PVOID&)TrueZwQuerySystemInformation, ZwQuerySystemInformationEx);
                  //提交事務
                  error = DetourTransactionCommit();
          ...

          掛鉤的原理

          從上述可以看到,Detours是通過事務確保了在DLL加載與卸載時后的原子性,但是如何確保多線程安全呢?后續通過調試去發現。

          可以利用x ntdl!ZwQuerySystemInformation查看函數地址,可以看到函數的未被掛鉤前的情況如下圖。

          掛鉤之后原始的指令被修改為一個跳轉指令把前八個字節覆蓋掉,剩余的3字節用垃圾指令填充。

          該地址里面又是一個jmp指令,并且完成間接尋址的跳轉。

          該地址是自定義函數ZwQuerySystemInformationEx,因此該間接跳轉是跳轉到的自定義函數內部。

          跳轉到TrueZwQuerySystemInformation內部發現ZwQuerySystemInformation函數內部的八字節指令被移動到該函數內部。緊接著又完成一個跳轉。

          該跳轉到ZwQuerySystemInformation函數內部緊接著完成ZwQuerySystemInformation函數的調用。

          綜上所述,整體流程如下圖。實際上Detours實際上使用的是熱補丁的思路,但是Detours并不是直接在原始的函數空間中進行補丁,而是開辟了一段臨時空間,將指令存儲在里面。因此在掛鉤后不需要進行脫鉤處理就可以調用原始函數。因此就不存在多線程中掛鉤與脫鉤的沖突。

          完整代碼:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/detoursHook.c

          覽器

          瀏覽器是一種軟件,它可以從遠程服務器(或本地磁盤)中加載文件并顯示文件,它可以允許用戶和它交互。

          瀏覽器的核心是瀏覽器引擎。在不同的瀏覽器中,根據瀏覽器引擎的不同,它們顯示頁面的內容或者順序會有所區別。

          火狐的瀏覽器引擎稱為Gecko,Chrome的瀏覽器引擎稱為Blink,而最新版的IE瀏覽器內核也已經投入Blink的懷抱。

          數據接收和轉換

          瀏覽器處理的是字節而不是我們寫的代碼,因此,它需要對我們的代碼比如html,css,js進行數據的轉換才能處理。

          HTML和DOM

          HTML是標記語言,它是由一個個閉合的標簽構成的,而瀏覽器需要將他們進行拆分統計,形成一個從根節點到子節點的數據結構,我們把這種結構叫做dom。可以說dom樹是瀏覽器的一個核心功能,正是有了dom樹的存在,我們的html代碼才有了層次和結構。

          CSS和CSSOM

          css是樣式表,它是用來給文檔添加樣式的。同HTML一樣,它也需要進行解析,它會解析成CSSOM樹,有了樹級結構,樣式就可以繼承和單獨拆分了。

          渲染樹

          DOM和CSSOM樹結構是兩個獨立的結構。

          DOM包含有關頁面HTML元素的關系的所有信息,而CSSOM包含有關元素樣式的信息。

          瀏覽器將DOM和CSSOM樹合并為一個稱為渲染樹的東西。有了它,我們才能看到豐富多彩的頁面。

          渲染樹包含有關頁面上所有可見DOM內容的信息,以及不同節點所需的所有CSSOM信息。

          請注意,如果元素被CSS隱藏,該節點將不會在渲染樹中表示。

          隱藏的元素將出現在DOM中,但不會出現在渲染樹中。

          原因是渲染樹結合了來自DOM和CSSOM的信息,因此它知道在樹中不包括隱藏元素。

          布局

          構建好渲染樹后,下一步就是執行“布局”。

          有了渲染樹之后,我們在屏幕上就擁有了所有可見內容的內容和樣式信息,但實際上我們還沒有在屏幕上呈現任何內容。

          瀏覽器需要通過從DOM和CSSOM接收到的內容和樣式計算頁面上每個對象的確切大小和位置。這樣才能顯示在頁面上。這個過程就是布局,也被稱為重排。

          繪制

          布局之后,我們需要的就是在瀏覽器中繪制出我們的頁面。

          根據渲染樹和布局之后的信息,瀏覽器只要一個個像素點進行繪制,就可以畫出整個頁面。

          JS和阻塞


          一個不錯的Web應用程序肯定會使用一些JavaScript。而且JavaScript可以修改頁面的內容和樣式。
          言外之意,JS可以從DOM樹中刪除和添加元素,也可以修改元素的CSSOM屬性。為什么很多時候我們都會把引入的js放到頁面底部,目的就是為了防止JS阻塞布局。因為瀏覽器是順序讀取的,所以越早的構造出渲染樹,我們看頁面的時間就越短。不過async異步的引入,讓我們可以不會因為js的加載而停止dom樹的構建,而是在js加載完成之后來使用。

          關鍵渲染路徑

          在接收HTML,CSS和JS字節并將它們轉換為屏幕上的渲染像素之間采取的步驟的過程稱為關鍵渲染路徑。

          優化您的網站的性能就是優化關鍵的渲染路徑。

          一個經過優化的站點應該進行漸進式渲染,并且不會阻塞整個過程。

          這就是網絡應用程序被視為慢速或快速的區別。

          總結

          現在的瀏覽器都提供開發者了開發者工具,通過它我們可以詳細了解到頁面的渲染,資源的加載整個過程,我們平時只要多打開它,多分析它,就能讓我們的網站以最快的方式呈現在客戶面前。

          <html>
          <head>
          <title>DOM 教程</title>
          </head>
          <body>
          <h1>DOM 第一課</h1>
          <p class="example">Hello world!</p>
          <input name="myInput" type="text" size="20" /><br />
          <h1 id="myHeader">This is a header</h1>
          </body>
          </html>

          上面的 HTML 中:

          <html> 節點沒有父節點;它是根節點

          <head> 和 <body> 的父節點是 <html> 節點

          文本節點 "Hello world!" 的父節點是 <p> 節點

          并且:

          <html> 節點擁有兩個子節點:<head> 和 <body>

          <head> 節點擁有一個子節點:<title> 節點

          <title> 節點也擁有一個子節點:文本節點 "DOM 教程"

          <h1> 和 <p> 節點是同胞節點, 同時也是 <body> 的子節點

          并且:

          <head> 元素是 <html> 元素的首個子節點

          <body> 元素是 <html> 元素的最后一個子節點

          <h1> 元素是 <body> 元素的首個子節點

          <p> 元素是 <body> 元素的最后一個子節點

          訪問節點:

          var oLi = document.getElementsByTagName("li");
          var oLi = document.getElementById("myHeader");
          var oLi = document.getElementsByName("myInput"); //通過name屬性訪問

          querySelector訪問方式: IE8開始支持, IE8以下不支持

          var div = document.querySelector("#myHeader"); //通過id訪問
          var div = document.querySelector("li"); //通過標簽訪問
          document.querySelector(".example"); //通過class屬性訪問

          獲取表單值

          document.getElementById(id).value

          querySelector() 方法返回文檔中匹配指定 CSS 選擇器的一個元素。

          注意: querySelector() 方法僅僅返回匹配指定選擇器的第一個元素。

          如果你需要返回所有的元素, 請使用 querySelectorAll() 方法替代。

          利用父子兄關系查找節點:

          使用childNodes屬性

          對象屬性

          nodeName 返回當前節點名字

          元素節點的 nodeName 是標簽名稱

          屬性節點的 nodeName 是屬性名稱

          文本節點的 nodeName 永遠是 #text

          文檔節點的 nodeName 永遠是 #document


          nodeValue 返回當前節點的值, 僅對文本節點和屬性節點

          對于文本節點, nodeValue 屬性包含文本。

          對于屬性節點, nodeValue 屬性包含屬性值。

          nodeValue 屬性對于文檔節點和元素節點是不可用的。

          注意:nodeValue與tagName的區別對于空白節點的返回值:nodeValue返回null, tagName返回undefined

          對于文本節點的返回值:nodeValue返回文本, tagName返回undefined

          nodeType 檢測節點類型:

          alert(document.nodeType);

          元素節點的nodeType值為1; 標簽名稱

          屬性節點的nodeType值為2; 屬性名稱 屬性節點不能算是其元素節點的子節點

          文本節點的nodeType值為3; #text

          注釋(Comment) 8: #comment

          文檔(Document) 9 #document <HTML>

          文檔類型(DocumentType) 10: <!DOCTYPE HTML PUBLIC"...">

          節點 nodeType nodeName nodeValue

          元素節點 1 大寫的標簽名 null

          屬性節點 2 屬性名 屬性值

          文本節點 3 #text 文本值

          tagName 返回標簽的名稱, 僅對元素節點

          parentNode 返回當前節點的父節點, 如果存在的話

          childNodes 返回當前節點的子節點集合

          firstChild 對標記的子節點集合中第一個節點的引用, 如果存在的話

          lastChild 對標記的子節點集合中最后一個節點的引用, 如果存在的話

          previousSibling 對同屬一個父節點的前一個兄弟節點的引用

          nextSibling 對同屬一個父節點的下一個兄弟節點的引用

          Attributes 返回當前節點(標記)屬性的列表 用于XML文件

          ownerDocument 返回節點所屬的根元素

          一些 DOM 對象方法

          getElementById() 返回帶有指定 ID 的元素。

          getElementsByTagName() 返回包含帶有指定標簽名稱的所有元素的節點列表(集合/節點數組)。

          getElementsByName() 返回包含帶有指定類名的所有元素的節點列表。

          appendChild() 把新的子節點添加到指定節點。

          removeChild() 刪除子節點。

          replaceChild() 替換子節點。

          insertBefore() 在指定的子節點前面插入新的子節點。

          createAttribute() 創建屬性節點。

          createElement() 創建元素節點。

          createTextNode() 創建文本節點。

          getAttribute() 返回指定的屬性值。

          setAttribute() 把指定屬性設置或修改為指定的值。

          刪除、替換、插入子節點必須通過父節點的removeChild()方法來完成的

          createAttribute() 創建屬性節點

          var att=document.createAttribute("class");
          att.value="democlass";
          document.getElementsByTagName("H1")[0].setAttributeNode(att);

          以上代碼可以簡化為

          document.getElementsByTagName("H1")[0].class="democlass";

          createAttribute()結合setAttributeNode()使用

          等同于:

          document.getElementsByTagName("H1")[0].setAttributeNode("class", "democlass");

          DOM獲取所有子節點:

          <html>
          <head>
          <title>childNodes</title>
          <script language="javascript">
          function myDOMInspector(){
          var oUl = document.getElementById("myList"); //獲取<ul>標記
          var DOMString = "";
          if(oUl.hasChildNodes()){ //判斷是否有子節點
          var oCh = oUl.childNodes;
          for(var i=0;i<oCh.length;i++) //逐一查找
          DOMString += oCh[i].nodeName + "\n";
          }
          alert(DOMString);
          }
          </script>
          </head>
          <body onload="myDOMInspector()">
          <ul id="myList">
          <li>糖醋排骨</li>
          <li>圓籠粉蒸肉</li>
          <li>泡菜魚</li>
          <li>板栗燒雞</li>
          <li>麻婆豆腐</li>
          </ul>
          </body>
          </html>

          使用parentNode屬性:

          <html>
          <head>
          <title>parentNode</title>
          <script language="javascript">
          function myDOMInspector(){
          var myItem = document.getElementById("myDearFood");
          alert(myItem.parentNode.tagName); //返回值為ul
          }
          </script>
          </head>
          <body onload="myDOMInspector()">
          <ul>
          <li>糖醋排骨</li>
          <li>圓籠粉蒸肉</li>
          <li>泡菜魚</li>
          <li id="myDearFood">板栗燒雞</li>
          <li>麻婆豆腐</li>
          </ul>
          </body>
          </html>

          DOM的兄弟關系:

          <html>
          <head>
          <title>Siblings</title>
          <script language="javascript">
          function myDOMInspector(){
          var myItem = document.getElementById("myDearFood");
          //訪問兄弟節點
          var nextListItem = myItem.nextSibling;
          var preListItem = myItem.previousSibling;
          alert(nextListItem.tagName +" "+ preListItem.tagName);
          }
          </script>
          </head>
          <body onload="myDOMInspector()">
          <ul>
          <li>糖醋排骨</li>
          <li>圓籠粉蒸肉</li>
          <li>泡菜魚</li>
          <li id="myDearFood">板栗燒雞</li>
          <li>麻婆豆腐</li>
          </ul>
          </body>
          </html>

          編寫自定義函數解決Firefox等瀏覽器包含眾多的空格作為文本節點問題。

          <html>
          <head>
          <title>Siblings</title>
          <script language="javascript">
          function nextSib(node){
          var tempLast = node.parentNode.lastChild;
          //判斷是否是最后一個節點,如果是則返回null
          if(node == tempLast)
          return null;
          var tempObj = node.nextSibling;
          //逐一搜索后面的兄弟節點,直到發現元素節點為止
          while(tempObj.nodeType!=1 && tempObj.nextSibling!=null)
          tempObj = tempObj.nextSibling;
          //三目運算符,如果是元素節點則返回節點本身,否則返回null
          return (tempObj.nodeType==1)?tempObj:null;
          }
          function prevSib(node){
          var tempFirst = node.parentNode.firstChild;
          //判斷是否是第一個節點,如果是則返回null
          if(node == tempFirst)
          return null;
          var tempObj = node.previousSibling;
          //逐一搜索前面的兄弟節點,直到發現元素節點為止
          while(tempObj.nodeType!=1 && tempObj.previousSibling!=null)
          tempObj = tempObj.previousSibling;
          return (tempObj.nodeType==1)?tempObj:null;
          }
          function myDOMInspector(){
          var myItem = document.getElementById("myDearFood");
          //獲取后一個元素兄弟節點
          var nextListItem = nextSib(myItem);
          //獲取前一個元素兄弟節點
          var preListItem = prevSib(myItem);
          alert("后一項:" + ((nextListItem!=null)?nextListItem.firstChild.nodeValue:null) + " 前一項:" + ((preListItem!=null)?preListItem.firstChild.nodeValue:null) );
          }
          </script>
          </head>
          <body onload="myDOMInspector()">
          <ul>
          <li>糖醋排骨</li>
          <li>圓籠粉蒸肉</li>
          <li>泡菜魚</li>
          <li id="myDearFood">板栗燒雞</li>
          <li>麻婆豆腐</li>
          </ul>
          </body>
          </html>

          注意:最新版的IE瀏覽器也包含眾多的空格作為文本節點;

          設置節點屬性:

          getAttribute()方法和setAttibute()方法

          <html>
          <head>
          <title>getAttribute()</title>
          <script language="javascript">
          function myDOMInspector(){
          //獲取圖片
          var myImg = document.getElementsByTagName("img")[0];
          //獲取圖片title屬性
          alert(myImg.getAttribute("title")); //也可以用myImg.title獲取屬性值
          }
          </script>
          </head>
          <body onload="myDOMInspector()">
          <img src="01.jpg" title="情人坡" />
          </body>
          </html>
          <html>
          <head>
          <title>setAttribute()</title>
          <script language="javascript">
          function changePic(){
          //獲取圖片
          var myImg = document.getElementsByTagName("img")[0];
          //設置圖片src和title屬性
          myImg.setAttribute("src","02.jpg"); //可以在屬性節點不存在時,添加節點的屬性值;
          myImg.setAttribute("title","紫荊公寓"); //也可以通過myImg.title="紫荊公寓";
          }
          </script>
          </head>
          <body>
          <img src="01.jpg" title="情人坡" onclick="changePic()" />
          </body>
          </html>

          setAttribute()設置HTML標簽的屬性

          oTable.setAttribute("border", "3"); //為表格邊框設置寬度
          oTable.setAttribute("border", 3);
          oTable.setAttribute("border", "3px"); //經過測試, 此種寫法也正確

          建議: 具體格式參照HTML屬性值的語法格式

          setAttibute()設置行內樣式

          obj.setAttribute("style", "position:absolute;left:200px;top:200px");

          注意:具體格式參考CSS樣式的語法格式

          setAttibute()設置事件屬性

          obj.setAttribute("onclick", "remove_img()"); //remove_img() 編寫自定義函數, 這里不能使用自定義函數

          注意:關于文本節點兼容性

          元素節點

          子節點: childNodes children

          首尾子節點: firstChild firstElementChild

          lastChild lastElementChild

          兄弟節點: nextSibling nextElementSibling

          previousSibling previousElementSibling

          childNodes firstChild lastChild nextSibling previousSibling屬性IE6-IE8版本瀏覽器不會返回空白節點,

          IE9以上版本瀏覽器會返回文本節點, W3C瀏覽器(包括火狐瀏覽器)也會返回文本節點

          children firstElementChild lastElementChild nextElementSibling previousElementSibling 只返回元素節點, 不會返回空白節點

          注意: DOM操作必須保住DOM節點必須存在, 當然也包括使用css樣式display:none隱藏的DOM節點, 否則會導致js語法錯誤;


          主站蜘蛛池模板: 久久蜜桃精品一区二区三区| 无码精品视频一区二区三区| 中文字幕日本精品一区二区三区 | 免费视频一区二区| 精品无码人妻一区二区三区品| 日本一区二区高清不卡| 亚洲午夜一区二区三区| 亚洲天堂一区二区| 亚洲无线码一区二区三区| 无码精品人妻一区| 韩国精品一区视频在线播放| 日本一区二区视频| 国产短视频精品一区二区三区| 中文字幕一区二区三区日韩精品| 国产一区二区三区免费看| 精品国产区一区二区三区在线观看| 骚片AV蜜桃精品一区| 亚洲国产成人一区二区精品区 | 精品无码一区二区三区爱欲九九| 亚洲日韩精品无码一区二区三区| 丰满人妻一区二区三区免费视频| 国产福利一区二区| 日本一区高清视频| 精品一区二区高清在线观看| 在线视频精品一区| 精品免费国产一区二区三区| 日本一区二区三区免费高清| 亚无码乱人伦一区二区| 亚洲综合av一区二区三区不卡| 人妻少妇精品视频三区二区一区| 亚洲熟妇AV一区二区三区浪潮| 亚洲av日韩综合一区久热| 无码丰满熟妇一区二区| 风间由美在线亚洲一区| 国产传媒一区二区三区呀| 无码AV一区二区三区无码| 国产一区在线视频| 国产精品特级毛片一区二区三区 | 黄桃AV无码免费一区二区三区 | 久久国产精品免费一区二区三区| 日韩精品一区二三区中文|