www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁 > 芯聞號 > 充電吧
[導(dǎo)讀]許多用戶都有過用Windows自帶的任務(wù)管理器查看所有進程的經(jīng)驗,并且很多人都認(rèn)為在任務(wù)管理器中隱藏進程是不可能的。而實際上,進程隱藏是再簡單不過的事情了。有許多可用的方法和參考源碼可以達到進程隱藏的

許多用戶都有過用Windows自帶的任務(wù)管理器查看所有進程的經(jīng)驗,并且很多人都認(rèn)為在任務(wù)管理器中隱藏進程是不可能的。而實際上,進程隱藏是再簡單不過的事情了。有許多可用的方法和參考源碼可以達到進程隱藏的目的。令我驚奇的是只有很少一部分的木馬使用了這種技術(shù)。估計1000個木馬中僅有1個是進程隱藏的。我認(rèn)為木馬的作者太懶了,因為隱藏進程需要進行的額外工作僅僅是對源代碼的拷貝-粘貼。所以我們應(yīng)該期待即將到來的會隱藏進程的木馬。

自然地,也就有必要研究進程隱藏的對抗技術(shù)。殺毒軟件和防火墻制造商就像他們的產(chǎn)品不能發(fā)現(xiàn)隱藏進程一樣落后了。在少之又少的免費工具中,能夠勝任的也只有Klister(僅運行于Windows 2000平臺)了。所有其他公司關(guān)注的只有金錢(俄文譯者kao注:不完全正確,F(xiàn)Secure的BlackLight Beta也是免費的)。除此之外,所有的這些工具都可以很容易的anti掉。

用程序?qū)崿F(xiàn)隱藏進程探測技術(shù),我們有兩種選擇:
* 基于某種探測原理找到一種隱藏的方法;
* 基于某個程序找到一種隱藏的方法,這個要簡單一些。

購買商業(yè)軟件產(chǎn)品的用戶不能修改程序,這樣可以保證其中綁定的程序的安全運行。因此第2種方法提到的程序就是商業(yè)程序的后門(rootkits)(例如hxdef Golden edition)。唯一的解決方案是創(chuàng)建一個免費的隱藏進程檢測的開源項目,這個程序使用幾種不同的檢測方法,這樣可以發(fā)現(xiàn)使用某一種方法進行隱藏的進程。任何一個用戶都可以抵擋某程序的捆綁程序,當(dāng)然那要得到程序的源代碼并且按照自己的意愿進行修改。

在這篇文章中我將討論探測隱藏進程的基本方法,列出該方法的示例代碼,并創(chuàng)建一個能夠檢測上面我們提到的隱藏進程的程序。

在用戶態(tài)(ring 3)檢測
我們從簡單的用戶態(tài)(ring 3)檢測開始,不使用驅(qū)動。事實上,每一個進程都會留下某種活動的痕跡,根據(jù)這些痕跡,我們就可以檢測到隱藏的進程。這些痕跡包括進程打開的句柄、窗口和創(chuàng)建的系統(tǒng)對象。要避開這種檢測技術(shù)是非常簡單的,但是這樣做需要留意進程留下所有痕跡,這種模式?jīng)]有被用在任何一個公開發(fā)行的后門(rootkits)上。(不幸的是內(nèi)部版本沒有對我開放)。用戶態(tài)方法容易實現(xiàn),使用安全,并且能夠得到很好的效果,因此這種方法不應(yīng)該被忽略。

首先我們定義一下用到的數(shù)據(jù),如下:

Code:

type
?PProcList = ^TProcList;
?TProcList = packed record
?? NextItem: pointer;
?? ProcName: array [0..MAX_PATH] of Char;
?? ProcId: dword;
?? ParrentId: dword;
?end;


使用ToolHelp API獲得所有進程列表
定義一下獲得進程列表的函數(shù)。我們要比較這個結(jié)果和通過其他途徑得到的結(jié)果:

Code:

{
?Acquiring list of processes by using ToolHelp API.
}
procedure GetToolHelpProcessList(var List: PListStruct);
var
?Snap: dword;
?Process: TPROCESSENTRY32;
?NewItem: PProcessRecord;
begin
? Snap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
? if Snap <> INVALID_HANDLE_VALUE then
???? begin
????? Process.dwSize := SizeOf(TPROCESSENTRY32);
????? if Process32First(Snap, Process) then
???????? repeat
????????? GetMem(NewItem, SizeOf(TProcessRecord));
????????? ZeroMemory(NewItem, SizeOf(TProcessRecord));
????????? NewItem^.ProcessId? := Process.th32ProcessID;
????????? NewItem^.ParrentPID := Process.th32ParentProcessID;
????????? lstrcpy(@NewItem^.ProcessName, Process.szExeFile);
????????? AddItem(List, NewItem);
???????? until not Process32Next(Snap, Process);
????? CloseHandle(Snap);
???? end;
end;

很明顯,這不會發(fā)現(xiàn)任何隱藏進程,所以這個函數(shù)只可以用來做探測隱藏進程的參考。

?

通過使用Native API獲得進程列表
再深一個層次的掃描我們要通過Native API ZwQuerySystemInformation獲得進程列表。雖然在這個級別(ring 0)什么也發(fā)現(xiàn)不了,但是我們?nèi)匀粦?yīng)該檢查一下。(prince注:有點令人費解,原文如下:The next scanning level will be acquisition a list of processes through

ZwQuerySystemInformation (Native API). It is improbable that something will be found out at this level but we should check it

anyway.)

Code:

{
?Acquiring list of processes by using ZwQuerySystemInformation.
}
procedure GetNativeProcessList(var List: PListStruct);
var
?Info: PSYSTEM_PROCESSES;
?NewItem: PProcessRecord;
?Mem: pointer;
begin
? Info := GetInfoTable(SystemProcessesAndThreadsInformation);
? Mem := Info;
? if Info = nil then Exit;
? repeat
?? GetMem(NewItem, SizeOf(TProcessRecord));
?? ZeroMemory(NewItem, SizeOf(TProcessRecord));
?? lstrcpy(@NewItem^.ProcessName,
?????????? PChar(WideCharToString(Info^.ProcessName.Buffer)));
?? NewItem^.ProcessId? := Info^.ProcessId;
?? NewItem^.ParrentPID := Info^.InheritedFromProcessId;
?? AddItem(List, NewItem);
?? Info := pointer(dword(info) + info^.NextEntryDelta);
? until Info^.NextEntryDelta = 0;
? VirtualFree(Mem, 0, MEM_RELEASE);
end;


通過進程打開的句柄獲得進程列表。
許多隱藏進程無法隱藏他們打開的句柄,因此我們可以通過使用ZwQuerySystemInformation函數(shù)枚舉打開的句柄來構(gòu)建進程列表。

Code:

{
? Acquiring the list of processes by using list of opened handles.
? Returns only ProcessId.
}
procedure GetHandlesProcessList(var List: PListStruct);
var
?Info: PSYSTEM_HANDLE_INFORMATION_EX;
?NewItem: PProcessRecord;
?r: dword;
?OldPid: dword;
begin
? OldPid := 0;
? Info := GetInfoTable(SystemHandleInformation);
? if Info = nil then Exit;
? for r := 0 to Info^.NumberOfHandles do
??? if Info^.Information[r].ProcessId <> OldPid then
???? begin
?????? OldPid := Info^.Information[r].ProcessId;
?????? GetMem(NewItem, SizeOf(TProcessRecord));
?????? ZeroMemory(NewItem, SizeOf(TProcessRecord));
?????? NewItem^.ProcessId?? := OldPid;
?????? AddItem(List, NewItem);
???? end;
? VirtualFree(Info, 0, MEM_RELEASE);
end;

到現(xiàn)在我們已經(jīng)可能發(fā)現(xiàn)一些東西了,但是我們不應(yīng)該依賴于像隱藏進程一樣簡單的隱藏句柄的檢查結(jié)果,盡管有些人甚至忘記隱藏他們。


通過列舉創(chuàng)建的窗口來得到進程列表。
可以將那在系統(tǒng)中注冊窗口的進程用GetWindowThreadProcessId構(gòu)建進程列表。

Code:

{
? Acquiring the list of processes by using list of windows.
? Returns only ProcessId.
}
procedure GetWindowsProcessList(var List: PListStruct);

?function EnumWindowsProc(hwnd: dword; PList: PPListStruct): bool; stdcall;
?var
? ProcId: dword;
? NewItem: PProcessRecord;
?begin
? GetWindowThreadProcessId(hwnd, ProcId);
?? if not IsPidAdded(PList^, ProcId) then
??? begin
???? GetMem(NewItem, SizeOf(TProcessRecord));
???? ZeroMemory(NewItem, SizeOf(TProcessRecord));
???? NewItem^.ProcessId?? := ProcId;
???? AddItem(PList^, NewItem);
? end;
? Result := true;
?end;

begin
?EnumWindows(@EnumWindowsProc, dword(@List));
end;

幾乎沒有人會隱藏窗口,因此這種檢查可以檢測某些進程,但是我們不應(yīng)該相信這種檢測。

直接通過系統(tǒng)調(diào)用得到進程列表。
在用戶態(tài)隱藏進程,一個普遍的做法是使用代碼注入(code-injection)技術(shù)和在所有進程中攔截ntdll.dll中的ZwQuerySystemInformation函數(shù)。
ntdll中的函數(shù)實際上對應(yīng)著系統(tǒng)內(nèi)核中的函數(shù)和系統(tǒng)調(diào)用(Windows 2000 中的2Eh中斷或者Windows XP中的sysenter指令),因此大多數(shù)簡單又有效的關(guān)于那些用戶級的隱藏進程的檢測方法就是直接使用系統(tǒng)調(diào)用而不是使用API函數(shù)。

Windows XP中ZwQuerySystemInformation函數(shù)的替代函數(shù)看起來是這個樣子:

Code:

{
?ZwQuerySystemInformation for Windows XP.
}
Function XpZwQuerySystemInfoCall(ASystemInformationClass: dword;
???????????????????????????????? ASystemInformation: Pointer;
???????????????????????????????? ASystemInformationLength: dword;
???????????????????????????????? AReturnLength: pdword): dword; stdcall;
asm
?pop ebp
?mov eax, $AD
?call @SystemCall
?ret $10
?@SystemCall:
?mov edx, esp
?sysenter
end;


由于不同的系統(tǒng)調(diào)用機制,Windows 2000的這部分代碼看起來有些不同。

Code:

{
? Системный вызов ZwQuerySystemInformation для Windows 2000.
}
Function Win2kZwQuerySystemInfoCall(ASystemInformationClass: dword;
??????????????????????????????????? ASystemInformation: Pointer;
??????????????????????????????????? ASystemInformationLength: dword;
??????????????????????????????????? AReturnLength: pdword): dword; stdcall;
asm
?pop ebp
?mov eax, $97
?lea edx, [esp + $04]
?int $2E
?ret $10
end;


現(xiàn)在有必要使用上面提到的函數(shù)而不是ntdll來枚舉系統(tǒng)進程了。實現(xiàn)的代碼如下:

Code:

{
? Acquiring the list of processes by use of a direct system call
? ZwQuerySystemInformation.
}
procedure GetSyscallProcessList(var List: PListStruct);
var
?Info: PSYSTEM_PROCESSES;
?NewItem: PProcessRecord;
?mPtr: pointer;
?mSize: dword;
?St: NTStatus;
begin
?mSize := $4000;
?repeat
? GetMem(mPtr, mSize);
? St := ZwQuerySystemInfoCall(SystemProcessesAndThreadsInformation,
????????????????????????????? mPtr, mSize, nil);
? if St = STATUS_INFO_LENGTH_MISMATCH then
??? begin
????? FreeMem(mPtr);
????? mSize := mSize * 2;
??? end;
?until St <> STATUS_INFO_LENGTH_MISMATCH;
?if St = STATUS_SUCCESS then
? begin
??? Info := mPtr;
??? repeat
???? GetMem(NewItem, SizeOf(TProcessRecord));
???? ZeroMemory(NewItem, SizeOf(TProcessRecord));
???? lstrcpy(@NewItem^.ProcessName,
???????????? PChar(WideCharToString(Info^.ProcessName.Buffer)));
???? NewItem^.ProcessId? := Info^.ProcessId;
???? NewItem^.ParrentPID := Info^.InheritedFromProcessId;
???? Info := pointer(dword(info) + info^.NextEntryDelta);
???? AddItem(List, NewItem);
??? until Info^.NextEntryDelta = 0;
? end;
?FreeMem(mPtr);
end;

這種方法能檢測幾乎100%的用戶態(tài)的后門(rootkits),例如hxdef的所有版本(包括黃金版)。

通過分析相關(guān)的句柄得到進程列表。
基于枚舉句柄的方法。這個方法的實質(zhì)并不是查找進程打開的句柄,而是查找同該進程相關(guān)的其他進程的句柄。這些句柄可以是進程句柄也可以是線程句柄。當(dāng)找到進程句柄,我們就可以用ZwQueryInformationProcess函數(shù)得到進程的PID。對于線程句柄,我們可以通過ZwQueryInformationThread得到進程ID。存在于系統(tǒng)中的所有進程都是由某些進程產(chǎn)生的,因此父進程擁有他們的句柄(除了那些已經(jīng)被關(guān)閉的句柄),對于Win32子系統(tǒng)服務(wù)器(csrss.exe)來說所有存在的進程的句柄都是可以訪問的。另外,Windows NT大量使用Job objects(prince: 任務(wù)對象?姑且這么翻譯吧,有不妥的地方請指教),任務(wù)對象可以關(guān)聯(lián)進程(比如屬于某用戶或服務(wù)的所有進程),因此當(dāng)找到任務(wù)對象的句柄,我們就可以利用它得到與之關(guān)聯(lián)的所有進程的ID。使用QueryInformationJobObject和信息類的函數(shù)JobObjectBasicProcessIdList就可以實現(xiàn)上述功能。利用分析進程相關(guān)的句柄得到進程列表的實現(xiàn)代碼如下:

Code:

{
?Acquiring the list of processes by analyzing handles in other processes.
}
procedure GetProcessesFromHandles(var List: PListStruct; Processes, Jobs, Threads: boolean);
var
?HandlesInfo: PSYSTEM_HANDLE_INFORMATION_EX;
?ProcessInfo: PROCESS_BASIC_INFORMATION;
?hProcess : dword;
?tHandle: dword;
?r, l???? : integer;
?NewItem: PProcessRecord;
?Info: PJOBOBJECT_BASIC_PROCESS_ID_LIST;
?Size: dword;
?THRInfo: THREAD_BASIC_INFORMATION;
begin
?HandlesInfo := GetInfoTable(SystemHandleInformation);
?if HandlesInfo <> nil then
?for r := 0 to HandlesInfo^.NumberOfHandles do
?? if HandlesInfo^.Information[r].ObjectTypeNumber in [OB_TYPE_PROCESS, OB_TYPE_JOB, OB_TYPE_THREAD] then
??? begin
????? hProcess? := OpenProcess(PROCESS_DUP_HANDLE, false,
?????????????????????????????? HandlesInfo^.Information[r].ProcessId);
??????????????????????????????
????? if DuplicateHandle(hProcess, HandlesInfo^.Information[r].Handle,
???????????????????????? INVALID_HANDLE_VALUE, @tHandle, 0, false,
???????????????????????? DUPLICATE_SAME_ACCESS) then
??????????? begin
???????????? case HandlesInfo^.Information[r].ObjectTypeNumber of
?????????????? OB_TYPE_PROCESS : begin
???????????????????? if Processes and (HandlesInfo^.Information[r].ProcessId = CsrPid) then
???????????????????? if ZwQueryInformationProcess(tHandle, ProcessBasicInformation,
??????????????????????????????????????????? @ProcessInfo,
??????????????????????????????????????????? SizeOf(PROCESS_BASIC_INFORMATION),
??????????????????????????????????????????? nil) = STATUS_SUCCESS then
???????????????????? if not IsPidAdded(List, ProcessInfo.UniqueProcessId) then
??????????????????????? begin
??????????????????????? GetMem(NewItem, SizeOf(TProcessRecord));
??????????????????????? ZeroMemory(NewItem, SizeOf(TProcessRecord));
??????????????????????? NewItem^.ProcessId?? := ProcessInfo.UniqueProcessId;
??????????????????????? NewItem^.ParrentPID? := ProcessInfo.InheritedFromUniqueProcessId;
??????????????????????? AddItem(List, NewItem);
??????????????????????? end;
???????????????????? end;

?????????????? OB_TYPE_JOB???? : begin
????????????????????????????????? if Jobs then
?????????????????????????????????? begin
??????????????????????????????????? Size := SizeOf(JOBOBJECT_BASIC_PROCESS_ID_LIST) + 4 * 1000;
??????????????????????????????????? GetMem(Info, Size);
??????????????????????????????????? Info^.NumberOfAssignedProcesses := 1000;
??????????????????????????????????? if QueryInformationJobObject(tHandle, JobObjectBasicProcessIdList,
???????????????????????????????????????????????????????????????? Info, Size, nil) then
?????????????????????????????????????? for l := 0 to Info^.NumberOfProcessIdsInList - 1 do
???????????????????????????????????????? if not IsPidAdded(List, Info^.ProcessIdList[l]) then
?????????????????????????????????????????? begin
??????????????????????????????????????????? GetMem(NewItem, SizeOf(TProcessRecord));
??????????????????????????????????????????? ZeroMemory(NewItem, SizeOf(TProcessRecord));
??????????????????????????????????????????? NewItem^.ProcessId?? := Info^.ProcessIdList[l];
??????????????????????????????????????????? AddItem(List, NewItem);
?????????????????????????????????????????? end;
??????????????????????????????????? FreeMem(Info);
?????????????????????????????????? end;
????????????????????????????????? end;

?????????????? OB_TYPE_THREAD? : begin
????????????????????????????????? if Threads then
????????????????????????????????? if ZwQueryInformationThread(tHandle, THREAD_BASIC_INFO,
????????????????????????????????????????????????????????????? @THRInfo,
????????????????????????????????????????????????????????????? SizeOf(THREAD_BASIC_INFORMATION),
????????????????????????????????????????????????????????????? nil) = STATUS_SUCCESS then
??????????????????????????????????? if not IsPidAdded(List, THRInfo.ClientId.UniqueProcess) then
???????????????????????????????????? begin
?????????????????????????????????????? GetMem(NewItem, SizeOf(TProcessRecord));
?????????????????????????????????????? ZeroMemory(NewItem, SizeOf(TProcessRecord));
?????????????????????????????????????? NewItem^.ProcessId?? := THRInfo.ClientId.UniqueProcess;
?????????????????????????????????????? AddItem(List, NewItem);
???????????????????????????????????? end;
???????????????????????????????? end;

???????????? end;
???????????? CloseHandle(tHandle);
??????????? end;
????????? CloseHandle(hProcess);
??????? end;
?VirtualFree(HandlesInfo, 0, MEM_RELEASE);
end;

不幸的是,上面提到的這些方法有些只能得到進程ID,而不能得到進程名字。因此,我們還需要通過進程ID得到進程的名稱。當(dāng)然,當(dāng)這些進程是隱藏進程的時候我們就不能使用ToolHelp API來實現(xiàn)。所以我們應(yīng)該訪問進程內(nèi)存通過讀取該進程的PEB得到進程名稱。PEB地址可以用ZwQueryInformationProcess函數(shù)獲得。以上所說的功能實現(xiàn)代碼如下:

Code:

function GetNameByPid(Pid: dword): string;
var
?hProcess, Bytes: dword;
?Info: PROCESS_BASIC_INFORMATION;
?ProcessParametres: pointer;
?ImagePath: TUnicodeString;
?ImgPath: array[0..MAX_PATH] of WideChar;
begin
?Result := ';
?ZeroMemory(@ImgPath, MAX_PATH * SizeOf(WideChar));
?hProcess := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, Pid);
?if ZwQueryInformationProcess(hProcess, ProcessBasicInformation, @Info,
????????????????????????????? SizeOf(PROCESS_BASIC_INFORMATION), nil) = STATUS_SUCCESS then
? begin
?? if ReadProcessMemory(hProcess, pointer(dword(Info.PebBaseAddress) + $10),
??????????????????????? @ProcessParametres, SizeOf(pointer), Bytes) and
????? ReadProcessMemory(hProcess, pointer(dword(ProcessParametres) + $38),
??????????????????????? @ImagePath, SizeOf(TUnicodeString), Bytes)? and
????? ReadProcessMemory(hProcess, ImagePath.Buffer, @ImgPath,
??????????????????????? ImagePath.Length, Bytes) then
??????? begin
????????? Result := ExtractFileName(WideCharToString(ImgPath));
??????? end;
?? end;
?CloseHandle(hProcess);
end;


當(dāng)然,用戶態(tài)隱藏進程的檢測方法不止這些,還可以想一些稍微復(fù)雜一點的新方法(比如,用SetWindowsHookEx函數(shù)對可訪問進程的注入和當(dāng)我們的DLL并成功加載后對進程列表的分析),但是現(xiàn)在我們將用上面提到的方法來解決問題。這些方法的優(yōu)點是他們可以簡單地編程實現(xiàn),并且除了可以檢測到用戶態(tài)的隱藏進程,還可以檢測到少數(shù)的在內(nèi)核態(tài)實現(xiàn)的隱藏進程... 要實現(xiàn)真正可靠的進程隱藏工具我們應(yīng)該使用Windows未公開的內(nèi)核數(shù)據(jù)結(jié)構(gòu)編寫內(nèi)核驅(qū)動程序。


內(nèi)核態(tài)(Ring 0)的檢測
恭喜你,我們終于開始進行內(nèi)核態(tài)隱藏進程的分析。內(nèi)核態(tài)的檢測方法同用戶態(tài)的檢測方法的主要區(qū)別是所有的進程列表都沒有使用API調(diào)用而是直接來自系統(tǒng)內(nèi)部數(shù)據(jù)結(jié)構(gòu)。在這些檢測方法下隱藏進程要困難得多,因為它們都是基于同Windows內(nèi)核相同的原理實現(xiàn)的,并且從這些內(nèi)核數(shù)據(jù)結(jié)構(gòu)中刪除進程將導(dǎo)致該進程完全失效。

內(nèi)核中的進程是什么?每一個進程都有自己的地址空間,描述符,線程等,內(nèi)核的數(shù)據(jù)結(jié)構(gòu)就涉及這些東西。每一個進程都是由EPROCESS結(jié)構(gòu)描述,而所有進程的結(jié)構(gòu)都被一個雙向循環(huán)鏈表維護。進程隱藏的一個方法就是改變進程結(jié)構(gòu)鏈表的指針,使得鏈表枚舉跳過自身達到進程隱藏的目的。避開進程枚舉并不影響進程的任何功能。無論怎樣,EPROCESS結(jié)構(gòu)總是存在的,對一個進程的正常功能來說它是必要的。在內(nèi)核態(tài)檢測隱藏進程的主要方法就是對這個結(jié)構(gòu)的檢查。

我們應(yīng)該定義一下將要儲存的進程信息的變量格式。這個變量格式應(yīng)該很方便地存儲來自驅(qū)動的數(shù)據(jù)(附錄)。結(jié)構(gòu)定義如下:

Code:

typedef struct _ProcessRecord
{
?? ULONG?????? Visibles;
?? ULONG?????? SignalState;
?? BOOLEAN???? Present;
?? ULONG?????? ProcessId;
?? ULONG?????? ParrentPID;
?? PEPROCESS?? pEPROCESS;
?? CHAR??????? ProcessName[256];
} TProcessRecord, *PProcessRecord;

應(yīng)該為這些結(jié)構(gòu)分配連續(xù)的大塊的內(nèi)存,并且不設(shè)置最后一個結(jié)構(gòu)的Present標(biāo)志。

在內(nèi)核中使用ZwQuerySystemInformation函數(shù)得到進程列表。

我們先從最簡單的方式開始,通過ZwQuerySystemInformation函數(shù)得到進程列表:

Code:

PVOID GetNativeProcessList(ULONG *MemSize)
{
?? ULONG PsCount = 0;
?? PVOID Info = GetInfoTable(SystemProcessesAndThreadsInformation);
?? PSYSTEM_PROCESSES Proc;
?? PVOID Mem = NULL;
?? PProcessRecord Data;

?? if (!Info) return NULL; else Proc = Info;

?? do
?? {
????? Proc = (PSYSTEM_PROCESSES)((ULONG)Proc + Proc->NextEntryDelta);??
????? PsCount++;
?? } while (Proc->NextEntryDelta);

?? *MemSize = (PsCount + 1) * sizeof(TProcessRecord);

?? Mem = ExAllocatePool(PagedPool, *MemSize);

?? if (!Mem) return NULL; else Data = Mem;
??
?? Proc = Info;
?? do
?? {
????? Proc = (PSYSTEM_PROCESSES)((ULONG)Proc + Proc->NextEntryDelta);
????? wcstombs(Data->ProcessName, Proc->ProcessName.Buffer, 255);
????? Data->Present??? = TRUE;
????? Data->ProcessId? = Proc->ProcessId;
????? Data->ParrentPID = Proc->InheritedFromProcessId;
????? PsLookupProcessByProcessId((HANDLE)Proc->ProcessId, &Data->pEPROCESS);
????? ObDereferenceObject(Data->pEPROCESS);
????? Data++;
?? } while (Proc->NextEntryDelta);

?? Data->Present = FALSE;

?? ExFreePool(Info);

?? return Mem;
}

以這個函數(shù)做參考,任何內(nèi)核態(tài)的隱藏進程都不會被檢測出來,但是所有的用戶態(tài)隱藏進程如hxdef是絕對逃不掉的。

在下面的代碼中我們可以簡單地用GetInfoTable函數(shù)來得到信息。為了防止有人問那是什么東西,下面列出完整的函數(shù)代碼。

Code:

/*
? Receiving buffer with results from ZwQuerySystemInformation.
*/
PVOID GetInfoTable(ULONG ATableType)
{
?? ULONG mSize = 0x4000;
?? PVOID mPtr = NULL;
?? NTSTATUS St;
?? do
?? {
????? mPtr = ExAllocatePool(PagedPool, mSize);
????? memset(mPtr, 0, mSize);
????? if (mPtr)
????? {
???????? St = ZwQuerySystemInformation(ATableType, mPtr, mSize, NULL);
????? } else return NULL;
????? if (St == STATUS_INFO_LENGTH_MISMATCH)
????? {
???????? ExFreePool(mPtr);
???????? mSize = mSize * 2;
????? }
?? } while (St == STATUS_INFO_LENGTH_MISMATCH);
?? if (St == STATUS_SUCCESS) return mPtr;
?? ExFreePool(mPtr);
?? return NULL;
}

我認(rèn)為這段代碼是很容易理解的...

利用EPROCESS結(jié)構(gòu)的雙向鏈表得到進程列表。
我們又進了一步。接下來我們將通過遍歷EPROCESS結(jié)構(gòu)的雙向鏈表來得到進程列表。鏈表的表頭是PsActiveProcessHead,因此要想正確地枚舉進程我們需要找到這個并沒有被導(dǎo)出的符號。在這之前我們應(yīng)該知道System進程是所有進程列表中的第一個進程。在DriverEntry例程開始時我們需要用PsGetCurrentProcess函數(shù)得到當(dāng)前進程的指針(使用SC管理器的API或者ZwLoadDriver函數(shù)加載的驅(qū)動始終都是加載到System進程的上下文中的),BLink在ActiveProcessLinks中的偏移將指向PsActiveProcessHead。像這樣:

Code:

PsActiveProcessHead = *(PVOID *)((PUCHAR)PsGetCurrentProcess + ActiveProcessLinksOffset + 4);


現(xiàn)在就可以遍歷這個雙向鏈表來創(chuàng)建進程列表了:

Code:

PVOID GetEprocessProcessList(ULONG *MemSize)
{
?? PLIST_ENTRY Process;
?? ULONG PsCount = 0;
?? PVOID Mem = NULL;
?? PProcessRecord Data;

?? if (!PsActiveProcessHead) return NULL;

?? Process = PsActiveProcessHead->Flink;

?? while (Process != PsActiveProcessHead)
?? {
????? PsCount++;
????? Process = Process->Flink;
?? }

?? PsCount++;

?? *MemSize = PsCount * sizeof(TProcessRecord);

?? Mem = ExAllocatePool(PagedPool, *MemSize);
?? memset(Mem, 0, *MemSize);

?? if (!Mem) return NULL; else Data = Mem;

?? Process = PsActiveProcessHead->Flink;

?? while (Process != PsActiveProcessHead)
?? {
????? Data->Present???? = TRUE;
????? Data->ProcessId?? = *(PULONG)((ULONG)Process - ActPsLink + pIdOffset);
????? Data->ParrentPID? = *(PULONG)((ULONG)Process - ActPsLink + ppIdOffset);
????? Data->SignalState = *(PULONG)((ULONG)Process - ActPsLink + 4);
????? Data->pEPROCESS?? = (PEPROCESS)((ULONG)Process - ActPsLink);
????? strncpy(Data->ProcessName, (PVOID)((ULONG)Process - ActPsLink + NameOffset), 16);?????
????? Data++;
?????? Process = Process->Flink;
??
?? }

?? return Mem;
}

為了得到進程名稱、ID和父進程ID,我們利用它們在EPROCESS結(jié)構(gòu)中的偏移地址(pIdOffset, ppIdOffset, NameOffset, ActPsLink)。這些偏移隨著Windows系統(tǒng)版本的不同而不同,因此我們要在進程檢測程序的代碼中進行區(qū)分后得到他們正確的值(附錄)。

任何一個通過API截取方式隱藏的進程都將被上面這個方法檢測出來。但是如果進程是通過DKOM(直接處理內(nèi)核對象 - Direct Kernel Object Manipulation)方式隱藏,那這個方法就失效了,因為這種進程都被從進程鏈表中刪掉了。

通過列舉調(diào)度程序(scheduler)中的線程得到進程列表。

對付這種隱藏進程(俄文翻譯kao注:這個地方原文寫的比較模糊,作者大概的意思應(yīng)該是“使用DKOM的方式檢測隱藏進程”)的其中一種檢測方式是通過調(diào)度程序(scheduler)中的線程列表來得到進程列表。Windows 2000有三個維護線程的雙向鏈表(KiWaitInListHead, KiWaitOutListHead, KiDispatcherReadyListHead)。前面兩個鏈表包含等待某種事件的線程,最后面的鏈表包含的是等待執(zhí)行的線程。我們處理這些鏈表,根據(jù)線程鏈表結(jié)構(gòu)ETHREAD中的偏移就可以得到一個線程的ETHREAD指針(俄文翻譯kao注:原文中這句話實在是太難懂了,希望我翻譯的正確)。這個結(jié)構(gòu)包括了很多進程相關(guān)指針,也就是結(jié)構(gòu)_KPROCESS *Process(0x44, 0x150)和結(jié)構(gòu)_EPROCESS *ThreadsProcess(0x22C, 這僅是Windows 2000中的偏移量)。前面兩個指針對于一個線程的功能性沒有任何影響,因此可以很容易修改它們來隱藏進程。相反,第三個指針是當(dāng)切換地址空間時調(diào)度程序(schedler)使用的指針,所以這個指針是不能修改的。我們就用它來找到擁有某個線程的進程。

Klister就是使用了這種檢測方法,它的最大的缺點就是只能在Windows 2000平臺上工作(但是在這個平臺上某個補丁包也會讓它失效)。導(dǎo)致這個情況發(fā)生的原因就是這種程序使用了硬編碼的線程鏈表地址,而在每個補丁包中這些地址可能都是不同的。

在程序中使用硬編碼地址是很糟糕的解決方案,操作系統(tǒng)的升級就會使你的程序無法正常工作,要盡量避免使用這種檢測方法。所以應(yīng)該通過分析那些使用了這些鏈表的內(nèi)核函數(shù)來動態(tài)地得到它們的地址。

首先我們試試看在Windows 2000平臺上找出KiWaitInListHead和KiWaitOutListHead.使用鏈表地址的函數(shù)KeWaitForSingleObject代碼如下:

Code:

.text:0042DE56???????????????? mov???? ecx, offset KiWaitInListHead
.text:0042DE5B???????????????? test??? al, al
.text:0042DE5D???????????????? jz????? short loc_42DE6E
.text:0042DE5F???????????????? cmp???? byte ptr [esi+135h], 0
.text:0042DE66???????????????? jz????? short loc_42DE6E
.text:0042DE68???????????????? cmp???? byte ptr [esi+33h], 19h
.text:0042DE6C???????????????? jl????? short loc_42DE73
.text:0042DE6E???????????????? mov???? ecx, offset KiWaitOutListHead

我們使用反匯編器(用我寫的LDasm)反匯編KeWaitForSingleObject函數(shù)來獲得這些地址。當(dāng)索引(pOpcode)指向指令“mov ecx,

KiWaitInListHead”,(pOpcode + 5)指向的就是指令“test al, al”,(pOpcode + 24)指向的就是“mov ecx, KiWaitOutListHead”。

這樣我們就可以通過索引(pOpcode + 1)和(pOpcode + 25)正確地得到KiWaitInListHead和KiWaitOutListHead的地址了。搜索地址的代碼

如下:

Code:

void Win2KGetKiWaitInOutListHeads()
{
?? PUCHAR cPtr, pOpcode;
?? ULONG Length;
??
?? for (cPtr = (PUCHAR)KeWaitForSingleObject;
??????? cPtr < (PUCHAR)KeWaitForSingleObject + PAGE_SIZE;
???????????? cPtr += Length)
?? {
????? Length = SizeOfCode(cPtr, &pOpcode);

????? if (!Length) break;
?????
????? if (*pOpcode == 0xB9 && *(pOpcode + 5) == 0x84 && *(pOpcode + 24) == 0xB9)
????? {
???????? KiWaitInListHead? = *(PLIST_ENTRY *)(pOpcode + 1);
???????? KiWaitOutListHead = *(PLIST_ENTRY *)(pOpcode + 25);
???????? break;
????? }
?? }

?? return;
}


在Windows 2000平臺下我們可以用同樣的方法得到KiDispatcherReadyListHead, 搜索KeSetAffinityThread函數(shù):

Code:

.text:0042FAAA???????????????? lea???? eax, KiDispatcherReadyListHead[ecx*8]
.text:0042FAB1???????????????? cmp???? [eax], eax


搜索KiDispatcherReadyListHead函數(shù)的代碼:

Code:

void Win2KGetKiDispatcherReadyListHead()
{
?? PUCHAR cPtr, pOpcode;
?? ULONG Length;
??
?? for (cPtr = (PUCHAR)KeSetAffinityThread;
??????? cPtr < (PUCHAR)KeSetAffinityThread + PAGE_SIZE;
???????????? cPtr += Length)
?? {
????? Length = SizeOfCode(cPtr, &pOpcode);

????? if (!Length) break;?????

????? if (*(PUSHORT)pOpcode == 0x048D && *(pOpcode + 2) == 0xCD && *(pOpcode + 7) == 0x39)
????? {
???????? KiDispatcherReadyListHead = *(PVOID *)(pOpcode + 3);
???????? break;
????? }
?? }

?? return;
}

不幸的是,Windows XP內(nèi)核完全不同于Windows 2000內(nèi)核。XP下的調(diào)度程序(scheduler)只有兩個線程鏈表:KiWaitListHead和

KiDispatcherReadyListHead。我們可以通過搜索KeDelayExecutionThread函數(shù)來查找KeWaitListHead:

Code:

.text:004055B5???????????????? mov???? dword ptr [ebx], offset KiWaitListHead
.text:004055BB???????????????? mov???? [ebx+4], eax


搜索代碼如下:

Code:

void XPGetKiWaitListHead()
{
?? PUCHAR cPtr, pOpcode;
?? ULONG Length;

?? for (cPtr = (PUCHAR)KeDelayExecutionThread;
??????? cPtr < (PUCHAR)KeDelayExecutionThread + PAGE_SIZE;
???????????? cPtr += Length)
?? {
????? Length = SizeOfCode(cPtr, &pOpcode);

????? if (!Length) break;

????? if (*(PUSHORT)cPtr == 0x03C7 && *(PUSHORT)(pOpcode + 6) == 0x4389)
????? {
???????? KiWaitInListHead = *(PLIST_ENTRY *)(pOpcode + 2);
???????? break;
????? }
?? }

?? return;
}

最困難的是查找KiDispatcherReadyListHead。主要的問題是KiDispatcherReadyListHead的地址并沒有被任何一個導(dǎo)出的函數(shù)使用。因此就要

用更加復(fù)雜的搜索算法搞定它。就從KiDispatchInterrupt函數(shù)開始,我們感興趣的地方只有這里:

Code:

.text:00404E72???????????????? mov???? byte ptr [edi+50h], 1
.text:00404E76???????????????? call??? sub_404C5A
.text:00404E7B???????????????? mov???? cl, 1
.text:00404E7D???????????????? call??? sub_404EB9


這段代碼中的第一個函數(shù)調(diào)用指向的就是包含KiDispatcherReadyListHead引用的函數(shù)。盡管如此,搜索KiDispatcherReadyListHead的地址卻

變的更加復(fù)雜,因為這個函數(shù)的相關(guān)代碼在Windows XP SP1和SP2中是不同的。在SP2中它是這個樣子:

Code:

.text:00404CCD???????????????? add???? eax, 60h
.text:00404CD0???????????????? test??? bl, bl
.text:00404CD2???????????????? lea???? edx, KiDispatcherReadyListHead[ecx*8]
.text:00404CD9???????????????? jnz???? loc_401F12
.text:00404CDF???????????????? mov???? esi, [edx+4]


And in SP1:
SP1中是這樣的:

Code:

.text:004180FE???????????????? add???? eax, 60h
.text:00418101???????????????? cmp???? [ebp+var_1], bl
.text:00418104???????????????? lea???? edx, KiDispatcherReadyListHead[ecx*8]
.text:0041810B???????????????? jz????? loc_418760
.text:00418111???????????????? mov???? esi, [edx]

僅僅查找一個“l(fā)ea”指令是不可靠的,因此我們也應(yīng)該檢查“l(fā)ea”后面的指令(LDasm中的IsRelativeCmd函數(shù))。搜索

KiDispatcherReadyListHead的全部代碼如下:

Code:

void XPGetKiDispatcherReadyListHead()
{
?? PUCHAR cPtr, pOpcode;
?? PUCHAR CallAddr = NULL;
?? ULONG Length;

?? for (cPtr = (PUCHAR)KiDispatchInterrupt;
??????? cPtr < (PUCHAR)KiDispatchInterrupt + PAGE_SIZE;
???????????? cPtr += Length)
?? {
????? Length = SizeOfCode(cPtr, &pOpcode);

????? if (!Length) return;

????? if (*pOpcode == 0xE8 && *(PUSHORT)(pOpcode + 5) == 0x01B1)
????? {
???????? CallAddr = (PUCHAR)(*(PULONG)(pOpcode + 1) + (ULONG)cPtr + Length);
???????? break;
????? }
?? }

?? if (!CallAddr || !MmIsAddressValid(CallAddr)) return;

?? for (cPtr = CallAddr; cPtr < CallAddr + PAGE_SIZE; cPtr += Length)
?? {
????? Length = SizeOfCode(cPtr, &pOpcode);

????? if (!Length) return;

????? if (*(PUSHORT)pOpcode == 0x148D && *(pOpcode + 2) == 0xCD && IsRelativeCmd(pOpcode + 7))
????? {
???????? KiDispatcherReadyListHead = *(PLIST_ENTRY *)(pOpcode + 3);
???????? break;
????? }
?? }

?? return;
}


找到線程鏈表地址之后我們就可以非常簡單地枚舉出那些進程了,代碼如下:

Code:

void ProcessListHead(PLIST_ENTRY ListHead)
{
?? PLIST_ENTRY Item;

?? if (ListHead)
?? {
????? Item = ListHead->Flink;

????? while (Item != ListHead)
????? {
???????? CollectProcess(*(PEPROCESS *)((ULONG)Item + WaitProcOffset));
???????? Item = Item->Flink;
????? }
?? }

?? return;
}

CollectProcess是一個非常有用的函數(shù),它可以增加一個進程到進程列表中去。


通過攔截系統(tǒng)調(diào)用得到進程列表。

任何一個進程都要通過API來和系統(tǒng)進行交互,而大多數(shù)交互都通過系統(tǒng)調(diào)用傳遞給了內(nèi)核。當(dāng)然,進程也可以不使用任何API而存在,但是這

樣一來它也就不能做任何有用(或有害)的事情。一般而言,我們的思路是使用系統(tǒng)調(diào)用管理器攔截系統(tǒng)調(diào)用,然后得到管理器中當(dāng)前進程的

EPROCESS指針。應(yīng)該在某段時間收集指針列表,這個表不會包含信息收集時沒有使用任何系統(tǒng)調(diào)用的進程(比如,進程的線程都處于等待狀態(tài)

)。

Windows 2000平臺使用2Eh中斷進行系統(tǒng)調(diào)用,因此我們需要修改IDT中的相應(yīng)的中斷描述符來攔截系統(tǒng)調(diào)用,這就要用sidt指令得到IDT在內(nèi)存

中的位置。該指令返回這樣一個結(jié)構(gòu):

Code:

typedef struct _Idt
{
?? USHORT Size;
?? ULONG? Base;
} TIdt;


修改2Eh中斷向量的代碼如下:

Code:

void Set2kSyscallHook()
{
?? TIdt Idt;
?? __asm
?? {
????? pushad
????? cli
????? sidt [Idt]
????? mov esi, NewSyscall
????? mov ebx, Idt.Base
????? xchg [ebx + 0x170], si
????? rol esi, 0x10
????? xchg [ebx + 0x176], si
????? ror esi, 0x10
????? mov OldSyscall, esi
????? sti
????? popad
?? }
}


當(dāng)然在卸載驅(qū)動之前還要保存原始狀態(tài)的信息:

Code:

void Win2kSyscallUnhook()
{
?? TIdt Idt;
?? __asm
?? {
????? pushad
????? cli
????? sidt [Idt]
????? mov esi, OldSyscall
????? mov ebx, Idt.Base
????? mov [ebx + 0x170], si
????? rol esi, 0x10
????? mov [ebx + 0x176], si
????? sti
????? xor eax, eax
????? mov OldSyscall, eax
????? popad
?? }
}


Windows XP使用sysenter/sysexit指令(出現(xiàn)在Pentium 2處理器中)實現(xiàn)系統(tǒng)調(diào)用。這些指令的功能由model-specific registers(MSR)控制

。系統(tǒng)調(diào)用管理器的地址保存在MSR寄存器,SYSENTER_EIP_MSR(0x176)中。用rdmsr指令讀取MSR寄存器,同時設(shè)置ECX = 要讀取的寄存器的號

碼,結(jié)果保存在兩個積存器EDX:EAX中。在我們這里,SYSENTER_EIP_MSR積存器是32位積存器,所以EDX為0,EAX內(nèi)是系統(tǒng)調(diào)用管理器的地址。

同樣地,我們也可以用wrmsr指令寫MSR積存器。有一個地方需要注意:當(dāng)寫32位MSR積存器的時候,EDX應(yīng)該被清空,否則將引起異常并且導(dǎo)致

系統(tǒng)立即崩潰。

考慮到所有的事情之后,替代系統(tǒng)調(diào)用管理器的代碼如下:

Code:

void SetXpSyscallHook()
{
?? __asm
?? {
????? pushad
????? mov ecx, 0x176
????? rdmsr
????? mov OldSyscall, eax
????? mov eax, NewSyscall
????? xor edx, edx
????? wrmsr
????? popad
?? }
}


恢復(fù)原始的系統(tǒng)調(diào)用管理器代碼:

Code:

void XpSyscallUnhook()
{
?? __asm
?? {
????? pushad
????? mov ecx, 0x176
????? mov eax, OldSyscall
????? xor edx, edx
????? wrmsr
????? xor eax, eax
????? mov OldSyscall, eax
????? popad
?? }
}


Windows XP的另外一個特性是它既可以使用sysenter也可以使用int 2Eh來進行系統(tǒng)調(diào)用,所以我們要替換這兩種情況下的系統(tǒng)調(diào)用管理器。

我們的新的系統(tǒng)調(diào)用管理器應(yīng)該得到當(dāng)前進程的EPROCESS指針,并且如果是一個新的進程,我們要把這個新的進程加到我們的進程列表中。
新的系統(tǒng)調(diào)用管理器代碼如下:

Code:

void __declspec(naked) NewSyscall()
{
?? __asm
?? {
????? pushad
????? pushfd
????? push fs
????? mov di, 0x30
????? mov fs, di
????? mov eax, fs:[0x124]
????? mov eax, [eax + 0x44]
????? push eax
????? call CollectProcess
????? pop fs
????? popfd
????? popad
????? jmp OldSyscall
?? }
}


得到進程列表的這段代碼應(yīng)該在某個時間段內(nèi)工作,所以我們有這樣的問題:如果在列表中的進程被刪除掉,在隨后的時間內(nèi)我們將保留一些

無效指針,結(jié)果就是檢測隱藏進程失敗或者導(dǎo)致系統(tǒng)BSOD。解決這個問題的辦法是,用PsSetCreateProcessNotifyRoutine函數(shù)注冊我們的回調(diào)

函數(shù),這個回調(diào)函數(shù)將會在系統(tǒng)創(chuàng)建或者銷毀一個進程的時候被調(diào)用。當(dāng)進程被銷毀時,我們也應(yīng)該把它從我們的表中刪除掉。
回調(diào)函數(shù)的原型如下:

Code:

VOID
(*PCREATE_PROCESS_NOTIFY_ROUTINE) (
??? IN HANDLE? ParentId,
??? IN HANDLE? ProcessId,
??? IN BOOLEAN? Create
??? );

安裝回調(diào)函數(shù)的代碼如下:

Code:

?PsSetCreateProcessNotifyRoutine (NotifyRoutine, FALSE);

取消回調(diào)函數(shù)的代碼:

Code:

?PsSetCreateProcessNotifyRoutine (NotifyRoutine, TRUE);


這里有一個問題,回調(diào)函數(shù)總是在系統(tǒng)被銷毀的時候創(chuàng)建,因此我們不可能直接在這個回調(diào)函數(shù)中刪除進程列表中的相應(yīng)進程。這樣我們就要

用系統(tǒng)的work items,首先調(diào)用IoAllocateWorkItem函數(shù)為work item分配內(nèi)存,然后調(diào)用IoQueueWorkItem函數(shù)(俄文翻譯者kao注:這一句我

不太確定...)將任務(wù)放置到工作線程隊列中。在處理過程中我們不僅僅從進程列表中刪除掉已經(jīng)終止的進程,而且還要加入新創(chuàng)建的線程。處

理代碼如下:

Code:

void WorkItemProc(PDEVICE_OBJECT DeviceObject, PWorkItemStruct Data)
{
?? KeWaitForSingleObject(Data->pEPROCESS, Executive, KernelMode, FALSE, NULL);
?
?? DelItem(&wLastItem, Data->pEPROCESS);

?? ObDereferenceObject(Data->pEPROCESS);

?? IoFreeWorkItem(Data->IoWorkItem);

?? ExFreePool(Data);

?? return;
}


void NotifyRoutine(IN HANDLE? ParentId,
?????????????????? IN HANDLE? ProcessId,
?????????????????? IN BOOLEAN Create)
{
?? PEPROCESS?????? process;
?? PWorkItemStruct Data;

?? if (Create)
?? {
????? PsLookupProcessByProcessId(ProcessId, &process);

????? if (!IsAdded(wLastItem, process)) AddItem(&wLastItem, process);

????? ObDereferenceObject(process);

?? } else
?? {
????? process = PsGetCurrentProcess();
??????
????? ObReferenceObject(process);

????? Data = ExAllocatePool(NonPagedPool, sizeof(TWorkItemStruct));

????? Data->IoWorkItem = IoAllocate

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉