Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
在之前幾篇文章已經(jīng)學(xué)習(xí)了解了幾種鉤取的方法
● 淺談?wù){(diào)試模式鉤取
● 淺談熱補丁
● 淺談內(nèi)聯(lián)鉤取原理與實現(xiàn)
● 導(dǎo)入地址表鉤取技術(shù)
這篇文章就利用鉤取方式完成進(jìn)程隱藏的效果。
在實現(xiàn)進(jìn)程隱藏時,首先需要明確遍歷進(jìn)程的方法。
CreateToolhelp32Snapshot函數(shù)用于創(chuàng)建進(jìn)程的鏡像,當(dāng)?shù)诙€參數(shù)為0時則是創(chuàng)建所有進(jìn)程的鏡像,那么就可以達(dá)到遍歷所有進(jìn)程的效果。
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
int main()
{
//設(shè)置編碼,便于后面能夠輸出中文
setlocale(LC_ALL, "zh_CN.UTF-8");
//創(chuàng)建進(jìn)程鏡像,參數(shù)0代表創(chuàng)建所有進(jìn)程的鏡像
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
std::cout << "Create Error" << std::endl;
exit(-1);
}
/*
* typedef struct tagPROCESSENTRY32 {
* DWORD dwSize; 進(jìn)程信息結(jié)構(gòu)體大小,首次調(diào)用之前必須初始化
* DWORD cntUsage; 引用進(jìn)程的次數(shù),引用次數(shù)為0時,則進(jìn)程結(jié)束
* DWORD th32ProcessID; 進(jìn)程的ID
* ULONG_PTR th32DefaultHeapID; 進(jìn)程默認(rèn)堆的標(biāo)識符,除工具使用對我們沒用
* DWORD th32ModuleID; 進(jìn)程模塊的標(biāo)識符
* DWORD cntThreads; 進(jìn)程啟動的執(zhí)行線程數(shù)
* DWORD th32ParentProcessID; 父進(jìn)程ID
* LONG pcPriClassBase; 進(jìn)程線程的基本優(yōu)先級
* DWORD dwFlags; 保留
* TCHAR szExeFile[MAX_PATH]; 進(jìn)程的路徑
* } PROCESSENTRY32;
* typedef PROCESSENTRY32 *PPROCESSENTRY32;
*/
PROCESSENTRY32 pi;
pi.dwSize = sizeof(PROCESSENTRY32);
//取出第一個進(jìn)程
BOOL bRet = Process32First(hSnapshot, &pi);
while (bRet)
{
wprintf(L"進(jìn)程路徑:%s\t進(jìn)程號:%d\n", pi.szExeFile, pi.th32ProcessID);
//取出下一個進(jìn)程
bRet = Process32Next(hSnapshot, &pi);
}
}
EnumProcesses用于將所有進(jìn)程號的收集。
#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;
//收集所有進(jìn)程的進(jìn)程號
if (!EnumProcesses(processes, sizeof(processes), &dwResult))
{
std::cout << "Enum Error" << std::endl;
}
//進(jìn)程數(shù)量
size = dwResult / sizeof(DWORD);
for (i = 0; i < size; i++)
{
//判斷進(jìn)程號是否為0
if (processes[i] != 0)
{
//用于存儲進(jìn)程路徑
TCHAR szProcessName[MAX_PATH] = { 0 };
//使用查詢權(quán)限打開進(jìn)程
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE,
processes[i]);
if (hProcess != NULL)
{
HMODULE hMod;
DWORD dwNeeded;
//收集該進(jìn)程的所有模塊句柄,第一個句柄則為文件路徑
if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),
&dwNeeded))
{
//根據(jù)句柄獲取文件路徑
GetModuleBaseName(hProcess, hMod, szProcessName,
sizeof(szProcessName) / sizeof(TCHAR));
}
wprintf(L"進(jìn)程路徑:%s\t進(jìn)程號:%d\n", szProcessName, processes[i]);
}
}
}
}
ZwQuerySystemInfomation函數(shù)是CreateToolhelp32Snapshot函數(shù)與EnumProcesses函數(shù)底層調(diào)用的函數(shù),也用于遍歷進(jìn)程信息。代碼參考https://cloud.tencent.com/developer/article/1454933
#include <iostream>
#include <Windows.h>
#include <ntstatus.h>
#include <winternl.h>
#pragma comment(lib, "ntdll.lib")
//定義函數(shù)指針
typedef NTSTATUS(WINAPI* NTQUERYSYSTEMINFORMATION)(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength
);
int main()
{
//設(shè)置編碼
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;
//獲取函數(shù)地址
ZwQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(ntdll_dll, "ZwQuerySystemInformation");
if (ZwQuerySystemInformation != NULL)
{
SYSTEM_BASIC_INFORMATION sbi = { 0 };
//查詢系統(tǒng)基本信息
NTSTATUS status = ZwQuerySystemInformation(SystemBasicInformation, (PVOID)&sbi, sizeof(sbi), NULL);
if (status == STATUS_SUCCESS)
{
wprintf(L"處理器個數(shù):%d\r\n", sbi.NumberOfProcessors);
}
else
{
wprintf(L"ZwQuerySystemInfomation Error\n");
}
DWORD dwNeedSize = 0;
BYTE* pBuffer = NULL;
wprintf(L"\t----所有進(jìn)程信息----\t\n");
PSYSTEM_PROCESS_INFORMATION psp = NULL;
//查詢進(jìn)程數(shù)量
status = ZwQuerySystemInformation(SystemProcessInformation, NULL, 0, &dwNeedSize);
if (status == STATUS_INFO_LENGTH_MISMATCH)
{
pBuffer = new BYTE[dwNeedSize];
//查詢進(jìn)程信息
status = ZwQuerySystemInformation(SystemProcessInformation, (PVOID)pBuffer, dwNeedSize, NULL);
if (status == STATUS_SUCCESS)
{
psp = (PSYSTEM_PROCESS_INFORMATION)pBuffer;
wprintf(L"\tPID\t線程數(shù)\t工作集大小\t進(jìn)程名\n");
do {
//獲取進(jìn)程號
wprintf(L"\t%d", psp->UniqueProcessId);
//獲取線程數(shù)量
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");
}
}
}
}
通過上述分析可以知道遍歷進(jìn)程的方式有三種,分別是利用CreateToolhelp32Snapshot、EnumProcesses以及ZwQuerySystemInfomation函數(shù)
但是CreateToolhelp32Snapshot與EnumProcesses函數(shù)底層都是調(diào)用了ZwQuerySystemInfomation函數(shù),因此我們只需要鉤取該函數(shù)即可。
由于測試環(huán)境是Win11,因此需要判斷在Win11情況下底層是否還是調(diào)用了ZwQuerySystemInfomation函數(shù)。
可以看到在Win11下還是會調(diào)用ZwQuerySystemInfomation函數(shù),在用戶態(tài)下該函數(shù)的名稱為NtQuerySystemInformation函數(shù)。
這里采用內(nèi)聯(lián)鉤取的方式對ZwQuerySystemInfomation進(jìn)行鉤取處理,具體怎么鉤取在淺談內(nèi)聯(lián)鉤取原理與實現(xiàn)已經(jīng)介紹過了,這里就不詳細(xì)說明了。這里對自定義的ZwQuerySystemInfomation函數(shù)進(jìn)行說明。
首先第一步需要進(jìn)行脫鉤處理,因為后續(xù)需要用到初始的ZwQuerySystemInfomation函數(shù),緊接著獲取待鉤取函數(shù)的地址即可。
...
//脫鉤
UnHook("ntdll.dll", "ZwQuerySystemInformation", g_pOrgBytes);
HMODULE hModule = GetModuleHandleA("ntdll.dll");
//獲取待鉤取函數(shù)的地址
PROC pfnOld = GetProcAddress(hModule, "ZwQuerySystemInformation");
//調(diào)用原始的ZwQuerySystemInfomation函數(shù)
NTSTATUS status = ((NTQUERYSYSTEMINFORMATION)pfnOld)(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
...
為了隱藏指定進(jìn)程,我們需要遍歷進(jìn)程信息,找到目標(biāo)進(jìn)程并且刪除該進(jìn)程信息實現(xiàn)隱藏的效果。這里需要知道的是進(jìn)程信息都存儲在SYSTEM_PROCESS_INFORMATION結(jié)構(gòu)體中,該結(jié)構(gòu)體是通過單鏈表對進(jìn)程信息進(jìn)行鏈接。因此我們通過匹配進(jìn)程名稱找到對應(yīng)的SYSTEM_PROCESS_INFORMATION結(jié)構(gòu)體,然后進(jìn)行刪除即可,效果如下圖。
通過單鏈表中刪除節(jié)點的操作,取出目標(biāo)進(jìn)程的結(jié)構(gòu)體。代碼如下
...
pCur = (PSYSTEM_PROCESS_INFORMATION)(SystemInformation);
while (true)
{
if (!lstrcmpi(pCur->ImageName.Buffer, L"test.exe"))
{
//需要隱藏的進(jìn)程是最后一個節(jié)點
if (pCur->NextEntryOffset == 0)
pPrev->NextEntryOffset = 0;
//不是最后一個節(jié)點,則將該節(jié)點取出
else
pPrev->NextEntryOffset += pCur->NextEntryOffset;
}
//不是需要隱藏的節(jié)點,則繼續(xù)遍歷
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
但是采用內(nèi)聯(lián)鉤取的方法去鉤取任務(wù)管理器就會出現(xiàn)一個問題,這里將斷點取消,利用內(nèi)聯(lián)鉤取的方式去隱藏進(jìn)程。
首先利用bl命令查看斷點
緊著利用 bc [ID]刪除斷點
在注入之后任務(wù)管理器會在拷貝的時候發(fā)生異常
在經(jīng)過一番調(diào)試后發(fā)現(xiàn),由于多線程共同執(zhí)行導(dǎo)致原本需要可寫權(quán)限的段被修改為只讀權(quán)限
在windbg可以用使用!vprot + address查看指定地址的權(quán)限,可以看到由于程序往只讀權(quán)限的地址進(jìn)行拷貝處理,所以導(dǎo)致了異常。
但是在執(zhí)行拷貝階段是先修改了該地址為可寫權(quán)限,那么導(dǎo)致該原因的情況就是其他線程執(zhí)行了權(quán)限恢復(fù)后切換到該線程中進(jìn)行寫,所以導(dǎo)致了這個問題。
因此內(nèi)聯(lián)鉤取是存在多線程安全的問題,此時可以使用微軟自己構(gòu)建的鉤取庫Detours,可以在鉤取過程中確保線程安全。
【----幫助網(wǎng)安學(xué)習(xí),需要網(wǎng)安學(xué)習(xí)資料關(guān)注我,私信回復(fù)“資料”免費獲取----】
① 網(wǎng)安學(xué)習(xí)成長路徑思維導(dǎo)圖
② 60+網(wǎng)安經(jīng)典常用工具包
③ 100+SRC漏洞分析報告
④ 150+網(wǎng)安攻防實戰(zhàn)技術(shù)電子書
⑤ 最權(quán)威CISSP 認(rèn)證考試指南+題庫
⑥ 超1800頁CTF實戰(zhàn)技巧手冊
⑦ 最新網(wǎng)安大廠面試題合集(含答案)
⑧ APP客戶端安全檢測指南(安卓+IOS)
項目地址: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掛鉤非常簡單,只需要根據(jù)下列順序,并且將自定義函數(shù)的地址與被掛鉤的地址即可完成掛鉤處理。
...
//用于確保在 DLL 注入或加載時,恢復(fù)被 Detours 修改的進(jìn)程鏡像,保持穩(wěn)定性
DetourRestoreAfterWith();
//開始一個新的事務(wù)來附加或分離
DetourTransactionBegin();
//進(jìn)行線程上下文的更新
DetourUpdateThread(GetCurrentThread());
//掛鉤
DetourAttach(&(PVOID&)TrueZwQuerySystemInformation, ZwQuerySystemInformationEx);
//提交事務(wù)
error = DetourTransactionCommit();
...
脫鉤
然后根據(jù)順序完成脫鉤即可。
...
//開始一個新的事務(wù)來附加或分離
DetourTransactionBegin();
//進(jìn)行線程上下文的更新
DetourUpdateThread(GetCurrentThread());
//脫鉤
DetourDetach(&(PVOID&)TrueZwQuerySystemInformation, ZwQuerySystemInformationEx);
//提交事務(wù)
error = DetourTransactionCommit();
...
從上述可以看到,Detours是通過事務(wù)確保了在DLL加載與卸載時后的原子性,但是如何確保多線程安全呢?后續(xù)通過調(diào)試去發(fā)現(xiàn)。
可以利用x ntdl!ZwQuerySystemInformation查看函數(shù)地址,可以看到函數(shù)的未被掛鉤前的情況如下圖。
掛鉤之后原始的指令被修改為一個跳轉(zhuǎn)指令把前八個字節(jié)覆蓋掉,剩余的3字節(jié)用垃圾指令填充。
該地址里面又是一個jmp指令,并且完成間接尋址的跳轉(zhuǎn)。
該地址是自定義函數(shù)ZwQuerySystemInformationEx,因此該間接跳轉(zhuǎn)是跳轉(zhuǎn)到的自定義函數(shù)內(nèi)部。
跳轉(zhuǎn)到TrueZwQuerySystemInformation內(nèi)部發(fā)現(xiàn)ZwQuerySystemInformation函數(shù)內(nèi)部的八字節(jié)指令被移動到該函數(shù)內(nèi)部。緊接著又完成一個跳轉(zhuǎn)。
該跳轉(zhuǎn)到ZwQuerySystemInformation函數(shù)內(nèi)部緊接著完成ZwQuerySystemInformation函數(shù)的調(diào)用。
綜上所述,整體流程如下圖。實際上Detours實際上使用的是熱補丁的思路,但是Detours并不是直接在原始的函數(shù)空間中進(jìn)行補丁,而是開辟了一段臨時空間,將指令存儲在里面。因此在掛鉤后不需要進(jìn)行脫鉤處理就可以調(diào)用原始函數(shù)。因此就不存在多線程中掛鉤與脫鉤的沖突。
完整代碼:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/detoursHook.c
瀏覽器是一種軟件,它可以從遠(yuǎn)程服務(wù)器(或本地磁盤)中加載文件并顯示文件,它可以允許用戶和它交互。
瀏覽器的核心是瀏覽器引擎。在不同的瀏覽器中,根據(jù)瀏覽器引擎的不同,它們顯示頁面的內(nèi)容或者順序會有所區(qū)別。
火狐的瀏覽器引擎稱為Gecko,Chrome的瀏覽器引擎稱為Blink,而最新版的IE瀏覽器內(nèi)核也已經(jīng)投入Blink的懷抱。
瀏覽器處理的是字節(jié)而不是我們寫的代碼,因此,它需要對我們的代碼比如html,css,js進(jìn)行數(shù)據(jù)的轉(zhuǎn)換才能處理。
HTML是標(biāo)記語言,它是由一個個閉合的標(biāo)簽構(gòu)成的,而瀏覽器需要將他們進(jìn)行拆分統(tǒng)計,形成一個從根節(jié)點到子節(jié)點的數(shù)據(jù)結(jié)構(gòu),我們把這種結(jié)構(gòu)叫做dom。可以說dom樹是瀏覽器的一個核心功能,正是有了dom樹的存在,我們的html代碼才有了層次和結(jié)構(gòu)。
css是樣式表,它是用來給文檔添加樣式的。同HTML一樣,它也需要進(jìn)行解析,它會解析成CSSOM樹,有了樹級結(jié)構(gòu),樣式就可以繼承和單獨拆分了。
DOM和CSSOM樹結(jié)構(gòu)是兩個獨立的結(jié)構(gòu)。
DOM包含有關(guān)頁面HTML元素的關(guān)系的所有信息,而CSSOM包含有關(guān)元素樣式的信息。
瀏覽器將DOM和CSSOM樹合并為一個稱為渲染樹的東西。有了它,我們才能看到豐富多彩的頁面。
渲染樹包含有關(guān)頁面上所有可見DOM內(nèi)容的信息,以及不同節(jié)點所需的所有CSSOM信息。
請注意,如果元素被CSS隱藏,該節(jié)點將不會在渲染樹中表示。
隱藏的元素將出現(xiàn)在DOM中,但不會出現(xiàn)在渲染樹中。
原因是渲染樹結(jié)合了來自DOM和CSSOM的信息,因此它知道在樹中不包括隱藏元素。
構(gòu)建好渲染樹后,下一步就是執(zhí)行“布局”。
有了渲染樹之后,我們在屏幕上就擁有了所有可見內(nèi)容的內(nèi)容和樣式信息,但實際上我們還沒有在屏幕上呈現(xiàn)任何內(nèi)容。
瀏覽器需要通過從DOM和CSSOM接收到的內(nèi)容和樣式計算頁面上每個對象的確切大小和位置。這樣才能顯示在頁面上。這個過程就是布局,也被稱為重排。
布局之后,我們需要的就是在瀏覽器中繪制出我們的頁面。
根據(jù)渲染樹和布局之后的信息,瀏覽器只要一個個像素點進(jìn)行繪制,就可以畫出整個頁面。
一個不錯的Web應(yīng)用程序肯定會使用一些JavaScript。而且JavaScript可以修改頁面的內(nèi)容和樣式。
言外之意,JS可以從DOM樹中刪除和添加元素,也可以修改元素的CSSOM屬性。為什么很多時候我們都會把引入的js放到頁面底部,目的就是為了防止JS阻塞布局。因為瀏覽器是順序讀取的,所以越早的構(gòu)造出渲染樹,我們看頁面的時間就越短。不過async異步的引入,讓我們可以不會因為js的加載而停止dom樹的構(gòu)建,而是在js加載完成之后來使用。
在接收HTML,CSS和JS字節(jié)并將它們轉(zhuǎn)換為屏幕上的渲染像素之間采取的步驟的過程稱為關(guān)鍵渲染路徑。
優(yōu)化您的網(wǎng)站的性能就是優(yōu)化關(guān)鍵的渲染路徑。
一個經(jīng)過優(yōu)化的站點應(yīng)該進(jìn)行漸進(jìn)式渲染,并且不會阻塞整個過程。
這就是網(wǎng)絡(luò)應(yīng)用程序被視為慢速或快速的區(qū)別。
現(xiàn)在的瀏覽器都提供開發(fā)者了開發(fā)者工具,通過它我們可以詳細(xì)了解到頁面的渲染,資源的加載整個過程,我們平時只要多打開它,多分析它,就能讓我們的網(wǎng)站以最快的方式呈現(xiàn)在客戶面前。
<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> 節(jié)點沒有父節(jié)點;它是根節(jié)點
<head> 和 <body> 的父節(jié)點是 <html> 節(jié)點
文本節(jié)點 "Hello world!" 的父節(jié)點是 <p> 節(jié)點
并且:
<html> 節(jié)點擁有兩個子節(jié)點:<head> 和 <body>
<head> 節(jié)點擁有一個子節(jié)點:<title> 節(jié)點
<title> 節(jié)點也擁有一個子節(jié)點:文本節(jié)點 "DOM 教程"
<h1> 和 <p> 節(jié)點是同胞節(jié)點, 同時也是 <body> 的子節(jié)點
并且:
<head> 元素是 <html> 元素的首個子節(jié)點
<body> 元素是 <html> 元素的最后一個子節(jié)點
<h1> 元素是 <body> 元素的首個子節(jié)點
<p> 元素是 <body> 元素的最后一個子節(jié)點
訪問節(jié)點:
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"); //通過標(biāo)簽訪問
document.querySelector(".example"); //通過class屬性訪問
獲取表單值
document.getElementById(id).value
querySelector() 方法返回文檔中匹配指定 CSS 選擇器的一個元素。
注意: querySelector() 方法僅僅返回匹配指定選擇器的第一個元素。
如果你需要返回所有的元素, 請使用 querySelectorAll() 方法替代。
利用父子兄關(guān)系查找節(jié)點:
使用childNodes屬性
對象屬性
nodeName 返回當(dāng)前節(jié)點名字
元素節(jié)點的 nodeName 是標(biāo)簽名稱
屬性節(jié)點的 nodeName 是屬性名稱
文本節(jié)點的 nodeName 永遠(yuǎn)是 #text
文檔節(jié)點的 nodeName 永遠(yuǎn)是 #document
nodeValue 返回當(dāng)前節(jié)點的值, 僅對文本節(jié)點和屬性節(jié)點
對于文本節(jié)點, nodeValue 屬性包含文本。
對于屬性節(jié)點, nodeValue 屬性包含屬性值。
nodeValue 屬性對于文檔節(jié)點和元素節(jié)點是不可用的。
注意:nodeValue與tagName的區(qū)別對于空白節(jié)點的返回值:nodeValue返回null, tagName返回undefined
對于文本節(jié)點的返回值:nodeValue返回文本, tagName返回undefined
nodeType 檢測節(jié)點類型:
alert(document.nodeType);
元素節(jié)點的nodeType值為1; 標(biāo)簽名稱
屬性節(jié)點的nodeType值為2; 屬性名稱 屬性節(jié)點不能算是其元素節(jié)點的子節(jié)點
文本節(jié)點的nodeType值為3; #text
注釋(Comment) 8: #comment
文檔(Document) 9 #document <HTML>
文檔類型(DocumentType) 10: <!DOCTYPE HTML PUBLIC"...">
節(jié)點 nodeType nodeName nodeValue
元素節(jié)點 1 大寫的標(biāo)簽名 null
屬性節(jié)點 2 屬性名 屬性值
文本節(jié)點 3 #text 文本值
tagName 返回標(biāo)簽的名稱, 僅對元素節(jié)點
parentNode 返回當(dāng)前節(jié)點的父節(jié)點, 如果存在的話
childNodes 返回當(dāng)前節(jié)點的子節(jié)點集合
firstChild 對標(biāo)記的子節(jié)點集合中第一個節(jié)點的引用, 如果存在的話
lastChild 對標(biāo)記的子節(jié)點集合中最后一個節(jié)點的引用, 如果存在的話
previousSibling 對同屬一個父節(jié)點的前一個兄弟節(jié)點的引用
nextSibling 對同屬一個父節(jié)點的下一個兄弟節(jié)點的引用
Attributes 返回當(dāng)前節(jié)點(標(biāo)記)屬性的列表 用于XML文件
ownerDocument 返回節(jié)點所屬的根元素
一些 DOM 對象方法
getElementById() 返回帶有指定 ID 的元素。
getElementsByTagName() 返回包含帶有指定標(biāo)簽名稱的所有元素的節(jié)點列表(集合/節(jié)點數(shù)組)。
getElementsByName() 返回包含帶有指定類名的所有元素的節(jié)點列表。
appendChild() 把新的子節(jié)點添加到指定節(jié)點。
removeChild() 刪除子節(jié)點。
replaceChild() 替換子節(jié)點。
insertBefore() 在指定的子節(jié)點前面插入新的子節(jié)點。
createAttribute() 創(chuàng)建屬性節(jié)點。
createElement() 創(chuàng)建元素節(jié)點。
createTextNode() 創(chuàng)建文本節(jié)點。
getAttribute() 返回指定的屬性值。
setAttribute() 把指定屬性設(shè)置或修改為指定的值。
刪除、替換、插入子節(jié)點必須通過父節(jié)點的removeChild()方法來完成的
createAttribute() 創(chuàng)建屬性節(jié)點
var att=document.createAttribute("class");
att.value="democlass";
document.getElementsByTagName("H1")[0].setAttributeNode(att);
以上代碼可以簡化為
document.getElementsByTagName("H1")[0].class="democlass";
createAttribute()結(jié)合setAttributeNode()使用
等同于:
document.getElementsByTagName("H1")[0].setAttributeNode("class", "democlass");
DOM獲取所有子節(jié)點:
<html>
<head>
<title>childNodes</title>
<script language="javascript">
function myDOMInspector(){
var oUl = document.getElementById("myList"); //獲取<ul>標(biāo)記
var DOMString = "";
if(oUl.hasChildNodes()){ //判斷是否有子節(jié)點
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的兄弟關(guān)系:
<html>
<head>
<title>Siblings</title>
<script language="javascript">
function myDOMInspector(){
var myItem = document.getElementById("myDearFood");
//訪問兄弟節(jié)點
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>
編寫自定義函數(shù)解決Firefox等瀏覽器包含眾多的空格作為文本節(jié)點問題。
<html>
<head>
<title>Siblings</title>
<script language="javascript">
function nextSib(node){
var tempLast = node.parentNode.lastChild;
//判斷是否是最后一個節(jié)點,如果是則返回null
if(node == tempLast)
return null;
var tempObj = node.nextSibling;
//逐一搜索后面的兄弟節(jié)點,直到發(fā)現(xiàn)元素節(jié)點為止
while(tempObj.nodeType!=1 && tempObj.nextSibling!=null)
tempObj = tempObj.nextSibling;
//三目運算符,如果是元素節(jié)點則返回節(jié)點本身,否則返回null
return (tempObj.nodeType==1)?tempObj:null;
}
function prevSib(node){
var tempFirst = node.parentNode.firstChild;
//判斷是否是第一個節(jié)點,如果是則返回null
if(node == tempFirst)
return null;
var tempObj = node.previousSibling;
//逐一搜索前面的兄弟節(jié)點,直到發(fā)現(xiàn)元素節(jié)點為止
while(tempObj.nodeType!=1 && tempObj.previousSibling!=null)
tempObj = tempObj.previousSibling;
return (tempObj.nodeType==1)?tempObj:null;
}
function myDOMInspector(){
var myItem = document.getElementById("myDearFood");
//獲取后一個元素兄弟節(jié)點
var nextListItem = nextSib(myItem);
//獲取前一個元素兄弟節(jié)點
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瀏覽器也包含眾多的空格作為文本節(jié)點;
設(shè)置節(jié)點屬性:
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];
//設(shè)置圖片src和title屬性
myImg.setAttribute("src","02.jpg"); //可以在屬性節(jié)點不存在時,添加節(jié)點的屬性值;
myImg.setAttribute("title","紫荊公寓"); //也可以通過myImg.title="紫荊公寓";
}
</script>
</head>
<body>
<img src="01.jpg" title="情人坡" onclick="changePic()" />
</body>
</html>
setAttribute()設(shè)置HTML標(biāo)簽的屬性
oTable.setAttribute("border", "3"); //為表格邊框設(shè)置寬度
oTable.setAttribute("border", 3);
oTable.setAttribute("border", "3px"); //經(jīng)過測試, 此種寫法也正確
建議: 具體格式參照HTML屬性值的語法格式
setAttibute()設(shè)置行內(nèi)樣式
obj.setAttribute("style", "position:absolute;left:200px;top:200px");
注意:具體格式參考CSS樣式的語法格式
setAttibute()設(shè)置事件屬性
obj.setAttribute("onclick", "remove_img()"); //remove_img() 編寫自定義函數(shù), 這里不能使用自定義函數(shù)
注意:關(guān)于文本節(jié)點兼容性
元素節(jié)點
子節(jié)點: childNodes children
首尾子節(jié)點: firstChild firstElementChild
lastChild lastElementChild
兄弟節(jié)點: nextSibling nextElementSibling
previousSibling previousElementSibling
childNodes firstChild lastChild nextSibling previousSibling屬性IE6-IE8版本瀏覽器不會返回空白節(jié)點,
IE9以上版本瀏覽器會返回文本節(jié)點, W3C瀏覽器(包括火狐瀏覽器)也會返回文本節(jié)點
children firstElementChild lastElementChild nextElementSibling previousElementSibling 只返回元素節(jié)點, 不會返回空白節(jié)點
注意: DOM操作必須保住DOM節(jié)點必須存在, 當(dāng)然也包括使用css樣式display:none隱藏的DOM節(jié)點, 否則會導(dǎo)致js語法錯誤;
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。