|
| |
| 现代dump技术及保护措施[Ms-Rem](下) |
|
[ 2007-1-29 12:40:00 | By: greatdong ] |
Dynamic unpacking
另一种对付dump的常用方法就是的dynamic packing。其思想就是,protector并不将受保护的程序完全unpack,而只是unpack一部分。首先unpack第一页,当快要进行完时protector对异常进行拦截并unpack所请求的页,这时它就可以将上一页从内存中删除。这样受保护进程的image在内存里从来都不曾完整过,因此一般的dumper是没法进行dump的。这种办法在protector程序Armadillo中被广泛使用,被其称为CopyMem。为了拦截异常并对代码进行解码,程序建立了独立的进程,这个进程使用DebugAPI来调试受护进程。
对于摘除这种保护,大多数cracker采用的方法都不是最优的。他们的方法就是reverse并patch掉protector的代码,以此来迫使它解出完整的代码。这实在是太费事了,更让人难以接受的是,在每个新版本中,作者们都会修改这种保护机制的代码,这样旧的dump法就不再有效,从而就被人们毫不犹豫地扔到博物馆里。但幸运的是,我们还有更简单的办法——从内部来dump进程。为此需要将dump的代码注入到受保护进程的地址空间中并读取它的内存。在此过程中将会出现异常,处理异常的时候protector会向我们呈现全部解密后的代码。这种摘除CopyMem保护的方法已经是可以想出的最简单的办法了。CopyMem至今仍在被使用,原因就是这种方法还没有广泛流行。
获取kernel module的dump
在逆向ring 0下的protector的时候经常需要dump已加载到核心内存的PE文件。例如,如果protector对系统内核进行了patch,我们可以将内核的dump与磁盘文件做个比较,从而很容易地发现这个情况。抑或是dump出protector的驱动程序并研究它的解密方法。就是做到这些就已经不太容易了,因为所有现在的protectors早已经学会了在所有系统结构体中删除对自身驱动的引用,以此来实现驱动的隐藏,所以dump这种驱动的最现实的办法就是dump所有核心内存,之后在此dump中查找我们需要的东西。但是这个办法常常又不是很有效,因为保护程序的设计者们已经停止了对驱动的pack(比如StarForce 3),而是转向了更有前景的保护方法,比如多态代码以及虚拟机。
初看上去,dump内核模块只需要用copymem进行拷贝就行了,但实际上并没有这么简单,因为这里存在着一个陷阱。若要试图从头到尾读取模块内存的话,所能得到的就只是个蓝屏。若要尝试为所需内存段创建MDL并执行MmProbeAndLockPages,则会引发异常,而如果调用MmBuildMdlForNonPagedPool,则MDL会被建立,但要读取它会再一次引发蓝屏。与此相关的是,在native PE文件里,section可以有个“Discardable as needed”属性,这样的section在Driver Entry结束后会被立即删除。这样的系统可以节约NonPaged Pool,因为NonPaged Pool在系统里可是稀缺的。于是尝试访问这个地址就会引发异常,这个异常SEH不予处理,于是引起系统的崩溃。所以在读取内存之前我们需要用MmIsAddressValid来验证地址的有效性。以下是安全读取核心内存的代码:
NTSTATUS DumpKernelMemory(PVOID SrcAddr, PVOID DstAddr, ULONG Size) { PMDL pSrcMdl, pDstMdl; PUCHAR pAddress, pDstDddress; NTSTATUS st = STATUS_UNSUCCESSFUL; ULONG r;
pSrcMdl = IoAllocateMdl(SrcAddr, Size, FALSE, FALSE, NULL);
if (pSrcMdl) { MmBuildMdlForNonPagedPool(pSrcMdl);
pAddress = MmGetSystemAddressForMdlSafe(pSrcMdl, NormalPagePriority);
if (pAddress) { pDstMdl = IoAllocateMdl(DstAddr, Size, FALSE, FALSE, NULL);
if (pDstMdl) { __try { MmProbeAndLockPages(pDstMdl, UserMode, IoWriteAccess);
pDstDddress = MmGetSystemAddressForMdlSafe( pDstMdl, NormalPagePriority);
if (pDstDddress) { memset(pDstDddress, 0, Size);
for (r = 1; r < Size; r++) { if (MmIsAddressValid(pAddress)) *pDstDddress = *pAddress; pAddress++; pDstDddress++; }
st = STATUS_SUCCESS; } MmUnlockPages(pDstMdl); }
__except(EXCEPTION_EXECUTE_HANDLER) { }
IoFreeMdl(pDstMdl); } }
IoFreeMdl(pSrcMdl); }
return st; }
除内存读取之外,我们还需要获取模块的信息来知道读取的地址。获取此信息的方法将在后面介绍。
使dump过程更为便利
编写自己的dumper绝对是个费事的活儿,所以我决定使用PE Tools,因为它考虑了许多PE文件的细节并能方便快捷地进行dump。所以我写了一个叫eXtreme dumper(与Extreme Protector相对)的plugin,这个plugin可以实现驱动dump和使用DllInjection的dump。为此我在PE Tools中拦截了ZwOpenProcess和ZwReadVirtualMemory函数并用自己的函数做了替换,它们都是用上面介绍的方法写成的。作为例子,我这里给出ZwReadVirtualMemory处理程序的代码:
function NewZwReadVirtualMemory(ProcessHandle: dword; BaseAddress: pointer; Buffer: pointer; BufferLength: dword; ReturnLength: pdword): NTStatus; stdcall; var hPipe, Bytes, Len: dword; Req: TXDumpRequest; PipeName: array [0..128] of Char; sPid: array [0..8] of Char; ProcessId: dword; Query: TDriverQuery; begin if DriverMethod then begin Result := dword(-1); Len := BufferLength; Query.QueryType := 2; Query.Param1 := ProcessHandle; Query.Param2 := dword(@Len); Query.Param3 := dword(BaseAddress); Query.Param4 := dword(Buffer); WriteFile(hDriver, Query, SizeOf(TDriverQuery), Bytes, nil); if ReturnLength <> nil then ReturnLength^ := Len; if Len > 0 then Result := STATUS_SUCCESS; end else begin ProcessId := GetPid(ProcessHandle); StrCpy(PipeName, bPipeName); ToHex(ProcessId, 8, sPid); StrCat(PipeName, sPid); hPipe := CreateFile(@PipeName, GENERIC_WRITE or GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0); if hPipe <> INVALID_HANDLE_VALUE then begin Req.Address := BaseAddress; Req.Length := BufferLength; WriteFile(hPipe, Req, SizeOf(TXDumpRequest), Bytes, nil); ReadFile(hPipe, Len, SizeOf(dword), Bytes, nil); ReadFile(hPipe, Buffer^, Len, Bytes, nil); if ReturnLength <> nil then ReturnLength^ := Len; Result := 0; CloseHandle(hPipe); end else Result := TrueZwReadVirtualMemory(ProcessHandle, BaseAddress, Buffer, BufferLength, ReturnLength); end; end;
我们看到,为了使dumper与注入的DLL互动在受保护的进程中使用了named pipes,其名字就是字符串eXtremeDumper和进程Id的HEX代码。读取内存的请求发向被dump的进程,在那里server端读取请求的内存并将数据和读取的字节数返回。dumper的server端的代码如下:
library xDump;
uses Windows, advApiHook, NativeApi;
{$include string.inc}
type PXDumpRequest = ^TXDumpRequest; TXDumpRequest = packed record ReqType: dword; Address: pointer; Length: dword; end;
const bPipeName = '\\.\pipe\eXtremeDumper'#0;
function SafeReadMemory(Addr, Buff: pointer; Size: dword): dword; asm push ebx push edx push ecx push esi push edi push ebp push offset @Handler push fs:[0] mov fs:[0], esp mov esi, eax mov edi, edx rep movsb mov eax, ecx pop fs:[0] add esp, 4 pop ebp pop edi pop esi pop ecx pop edx pop ebx ret @handler: mov ecx, [esp + $0C] add [ecx + $B8], 2 ret end;
function PipeThread(hPipe: dword): dword; stdcall; var Req: TXDumpRequest; Bytes, Len: dword; pBuff: pointer; begin ReadFile(hPipe, Req, SizeOf(TXDumpRequest), Bytes, nil); GetMem(pBuff, Req.Length); Len := Req.Length - SafeReadMemory(Req.Address, pBuff, Req.Length); WriteFile(hPipe, Len, SizeOf(dword), Bytes, nil); WriteFile(hPipe, pBuff^, Req.Length, Bytes, nil); FreeMem(pBuff); CloseHandle(hPipe); end;
var TrId: dword;
procedure PipeServerThread(); var hPipe: dword; PipeName: array [0..128] of Char; sPid: array [0..8] of Char; begin StrCpy(PipeName, bPipeName); ToHex(GetCurrentProcessId(), 8, sPid); StrCat(PipeName, sPid); repeat hPipe := CreateNamedPipe(PipeName, PIPE_ACCESS_DUPLEX or WRITE_DAC, PIPE_TYPE_MESSAGE or PIPE_READMODE_MESSAGE or PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 1024, 1024, 5000, nil); if hPipe = INVALID_HANDLE_VALUE then Exit; if ConnectNamedPipe(hPipe, nil) then CreateThread(nil, 0, @PipeThread, pointer(hPipe), 0, TrId); until false; end;
begin CreateThread(nil, 0, @PipeServerThread, nil, 0, TrId); end.
在client端连接pipe时会创建一个线程,该线程在SEH处理程序内部读取请求的数据,此后数据就流向client端。此代码唯一的缺点就是效率比较低,因为每个请求都是由单独的线程服务的。但是,实际上太高的效率也是多余,因为重要的是dump的获取,尽管有人会不满足,会编写更好的版本,但我这里就费点傻劲吧 :)。
现在我们回到内核模块信息的获取上来。为了实现这个目的,我将使用函数ZwQuerySystemInformation,class为SystemModuleInformation,这样函数会向我返回以下类型的结构体数组:
PSYSTEM_MODULE_INFORMATION = ^SYSTEM_MODULE_INFORMATION; SYSTEM_MODULE_INFORMATION = packed record // Information Class 11 Reserved: array[0..1] of ULONG; Base: PVOID; Size: ULONG; Flags: ULONG; Index: USHORT; Unknown: USHORT; LoadCount: USHORT; ModuleNameOffset: USHORT; ImageName: array [0..255] of Char; end;
PSYSTEM_MODULE_INFORMATION_EX = ^SYSTEM_MODULE_INFORMATION_EX; SYSTEM_MODULE_INFORMATION_EX = packed record ModulesCount: dword; Modules: array[0..0] of SYSTEM_MODULE_INFORMATION; end;
我们感兴趣的域是ImageName、Base和Size。我发现,在KernelMode下还可以通过链表PsLoadedModulesList来获取这项信息,但是用在这里就没什么意义了。
为了枚举进程加载的模块,PE Tools使用了Procs32.dll中的函数GetModuleFirst/GetModuleNext。为了映射自己的列表,我们将在System进程zhong拦截并替换成我们的版本。处理这些函数的代码如下:
function NewGetModuleNext(dwPID: dword; mEntry: PMODULE_ENTRY): bool; stdcall; begin if dwPID = SystemPid then begin Result := false; lstrcpy(mEntry^.lpFileName, Modules^.Modules[CurrentModule].ImageName); mEntry^.dwImageBase := dword(Modules^.Modules[CurrentModule].Base); mEntry^.dwImageSize := Modules^.Modules[CurrentModule].Size; mEntry^.bSystemProcess := true; Inc(CurrentModule); if CurrentModule > Modules^.ModulesCount then ReleaseModulesInfo() else Result := true; end else Result := TrueGetModuleNext(dwPID, mEntry); end;
function NewGetModuleFirst(dwPID: dword; mEntry: PMODULE_ENTRY): bool; stdcall; begin if dwPID = SystemPid then begin if CurrentModule > 0 then ReleaseModulesInfo(); Modules := GetInfoTable(SystemModuleInformation); Result := NewGetModuleNext(dwPID, mEntry); end else Result := TrueGetModuleFirst(dwPID, mEntry); end;
为了进行dump,PE Tools使用了NDump.dll中的DumpProcess函数,我们需要拦截这个函数并将dump请求发向我们自己的驱动里。处理程序的代码如下:
function NewDumpProcess(dwProcessId: dword; pStartAddr: pointer; dwcBytes: dword; pDumpedBytes: pointer ): bool; stdcall; var Query: TDriverQuery; Bytes: dword; begin if dwProcessId = SystemPid then begin Query.QueryType := IOCTL_DUMP_KERNEL_MEM; Query.Param1 := dword(pStartAddr); Query.Param2 := dwcBytes; Query.Param3 := dword(pDumpedBytes); Result := WriteFile(hDriver, Query, SizeOf(TDriverQuery), Bytes, nil); end else Result := TrueDumpProcess(dwProcessId, pStartAddr, dwcBytes, pDumpedBytes); end;
--------------------------------------------------------------------------------
eXtreme dumper可以分为以下部分:
两种dump的方法(Dll-Injection法和驱动法) 获取句柄的隐式方法(OpenProcessEx) 防止PE Tools受其它进程影响(Protect PE Tools) 对内核模块的dump(Enable kernel modules dumping)
简言之,这个plugin无疑是很有用的。
--------------------------------------------------------------------------------
我想提醒的是,写破解程序这类文章的作者们请不要进行违反俄联邦法规的活动。本文内容只能用于教学目的,对任何由此引起的非法行为,本作者不负有任何责任(对使用本译文进行违反中华人民共和国法规的行为,译者亦不负任何责任哦)。
[C] Ms-Rem
董岩 译 http://greatdong.blog.edu.cn |
|
| |
| 时 间 记 忆 |
<< < 2007 - 1 > >>
| 日 |
一 |
二 |
三 |
四 |
五 |
六 |
|
1 |
2 |
3 |
4 |
5 |
6 |
| 7 |
8 |
9 |
10 |
11 |
12 |
13 |
| 14 |
15 |
16 |
17 |
18 |
19 |
20 |
| 21 |
22 |
23 |
24 |
25 |
26 |
27 |
| 28 |
29 |
30 |
31 |
|
|
|
|
|
|