接着 声声慢 的继续。。。续:http://bbs.pediy.com/showthread.php...;threadid=12834
写这篇文章是为了好玩,不要太认真的对待它。 前段时间有个朋友(Littleluk)问我是否有兴趣帮助他写脱themida (xprotector商业后继者)的壳的程序。 我同意了。 然而,我却对它有另外的一个想法.因为坦诚的来讲,我不想把我的时间浪费在无用的事情上,而themida就是其中的一件.对那些还不知道我在讲什么的人: themida是一个壳,而且它是在kernel形式下运行的壳.这意味着:要是您要用调试器去调试themida,它将要崩溃您的系统.很不专业,我知道.这是我把themida看作逆向人士的玩具的主要原因.直接的说,themida将永远不会被大量的公司使用,没有人会用这样一个给微软的kernel,SDT,IDT注补丁,时常令机器重启动等等的壳来保护一个很好的软件.这意味这它不会被广泛使用,除了对它本身的脱壳乐趣,不会有太大的意义.我对使用无意义的东西没有兴趣,但既然Littleluk提出,我就编写出一些东西(使用一些我以前编的旧代码).因此,这篇文章是围绕着我所写的一个工具.这不是脱themida壳的工具(那是不可能的:Littleluk还没有成功的逆向它).这个工具能使您电脑上已有的一些工具来防卫themida.
这里有一些警告:
1 - 这篇文章没有牵扯到核心部分的操作,你不需要有驱动编程的知识,这对那些驱动的编写者来说有些荒谬(而且我会说 '因为在做逆向的人中你可以很容易得找到对核心编程一无所知的人)。
2 - 在这篇文章中尼了解到的东西会十几年前的东西(就像 themida 使用的那些陷阱一样)。
3 - 我从来就没碰过 themida,我能写出这个工具并不是因为我的经验,而且结果是从 Littleluk 那里的来的。
4 - 我不会去解释什么是 SDT、IDT 以及其他内在相关的概念的含义。如果你不知道这些涵义的话,可以去网上找找。我也没时间去解释那些不太重要的,事实上这也是我现在放出这篇文章的原因之一。我刚好有几天的时间然后又得回去工作(现实中的工作)。
5 - 我会解释我所知道的 themida,或更准确地说你需要理解我写的那个工具。我不打算逆向 themida,所以对于很多事情我并不是很关心。
6 - 我不知道会不会有什么其它的 AntiMida,这完全取决于逆向工作者提供给我的信息量以及我的空余时间。
7 - 这个方法在你使用 PAE 扩展的时候不起作用(我已经懒得再去添加一些代码了)。
8 - 这个工具的工作方式也不是绝对的,还是有其他的方法的。
这次用于测试的牺牲品不是什么其它的东西而是 themida 自身(我是指试用版)。你可以从他的官方网站上下载他(当前版本是 1.0.0.5)。
AntiMida 不是一个计划过的工具,只是每次我遇到问题时而编写的解决方案。现在 AntiMida 可以让你:
1 - 使用普通用户模式的应用程序去转存 themida.
2 - 使用 imprec, winhex (察看程序的内存), 等等。
3 - 监视文件及注册表的访问。
但是一次最多做一个。第一步是转存 themida。怎么做?首先我们需要知道 themida 用于保护它自己防止被转存的方法。实际上首先想到去用的是 KeAttachProcess,我们转存了 ntoskrnl (有点痛苦) 然后看到 keattachprocess 被改成了一个 jmp 到 themida 入口。所以要使用 keattachprocess 的话,就必须先修改 ntoskrnl(我在下面贴出了代码)。下面是我使用 KeAttachProcess 写的入口:
case CODE_READ_MEM:
{
MemReadInfo mri;
ULONG_PTR ptr, addr;
BYTE *Buffer;
UINT x, y;
RtlCopyMemory(&mri, pInput, sizeof (MemReadInfo));
if (PsLookupProcessByProcessId(mri.PID, &ptr) != STATUS_SUCCESS)
return STATUS_INVALID_PARAMETER;
Buffer = (BYTE *) ExAllocatePool(NonPagedPool, dwOutputSize);
if (Buffer == NULL)
return STATUS_INVALID_PARAMETER;
KeAttachProcess(ptr);
if (dwOutputSize <= 0x1000)
{
x = 1;
}
else
{
x = dwOutputSize / 0x1000;
if (dwOutputSize % 0x1000 != 0)
x++;
}
for (y = 0; y < x; y++)
{
addr = y * 0x1000 + (ULONG_PTR) mri.Address;
if (MmIsAddressValid((PVOID) addr) == FALSE)
{
ExFreePool(Buffer);
return STATUS_INVALID_PARAMETER;
}
}
RtlCopyMemory(Buffer, mri.Address, dwOutputSize);
KeDetachProcess();
RtlCopyMemory(pOutput, Buffer, dwOutputSize);
ExFreePool(Buffer);
*pdwInfo = dwOutputSize;
break;
}
我贴这段代码只是为了信息,因为这段代码现在已经不在 AntiMida 中了。很显然 themida 正在关联 SDT,然后在使用 sdtrestore 扫描之后我得到了一个被关联的服务的列表:
ZwAllocateVirtualMemory 11 --[未知关联于 F5938BC4]--
ZwCreateThread 35 --[未知关联于 F5938CBE]--
ZwDebugContinue 3A --[未知关联于 F59391A0]--
ZwQueryVirtualMemory B2 --[未知关联于 F5938ACA]--
ZwReadVirtualMemory BA --[未知关联于 F5938014]--
ZwTerminateProcess 101 --[未知关联于 F59389D0]--
ZwWriteVirtualMemory 115 --[未知关联于 F5938000]--
很好 ZwAllocateVirtualMemory, ZwQueryVirtualMemory, ZwReadVirtualMemory, ZwWriteVirtualMemory, ZwCreateThread 是有用的,因为我们需要这些函数来转存 themida。ZwDebugContinue 是用来避开调试的,虽然 themida 也对 IDT 进行了一番胡搞(所以这可能就是反调试的手段,当然可能太简单了些)。ZwTerminateProcess 不是重要的,我猜他被关联只是为了能知道被保护的进程退出了。不过如果你尝试恢复这些服务中的任何一个的话,themida 就会整垮你的系统。
我想到的注意使制作一个工具室的其他已经存在的工具可以使用。所以我构建了一个列有当前运行的进程的列表,并提供选择其中一个使其抗 themida 的可能,这意味着对 themida 的免疫。一个简洁的界面还是要花上不少代码的。
当那个小按钮被按下时,一个 dll 会被注入选中的进程的地址空间,被 themida 关联以保护它自己的函数会被重定向到这个注入的 dll 上,每当调用一个被关联的函数时,这个 dll 会调用驱动来完成操作。那么我的驱动是怎么完成这些操作的呢?稍后你就会看到。现在先看看注入过程:
BOOL InjectModule(IN ULONG_PTR ProcessID, IN TCHAR *ModuleName,
OUT ULONG_PTR *BaseAddress OPTIONAL)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
ProcessID);
if (hProcess == NULL)
return FALSE;
HANDLE hFile = CreateFile(ModuleName, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
CloseHandle(hProcess);
return FALSE;
}
UINT PE_Size = GetFileSize(hFile, NULL);
BYTE *PE_Buffer = (BYTE *) VirtualAlloc(NULL, PE_Size,
MEM_COMMIT, PAGE_READWRITE);
if (PE_Buffer == NULL)
{
CloseHandle(hProcess);
CloseHandle(hFile);
return FALSE;
}
DWORD BR;
if (!ReadFile(hFile, PE_Buffer, PE_Size, &BR, NULL))
{
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
CloseHandle(hFile);
return FALSE;
}
CloseHandle(hFile);
BYTE *PE_Image;
IMAGE_DOS_HEADER *ImgDosHdr;
IMAGE_NT_HEADERS *ImgNtHdrs;
ULONG_PTR ImgBase, Delta, Reloc_Offset;
IMAGE_BASE_RELOCATION *ImgBaseReloc;
WORD *wData;
UINT i, nItems;
ULONG_PTR Offset;
DWORD Type;
ULONG_PTR *Block, BlockOffs;
ULONG_PTR IT_Offset;
IMAGE_IMPORT_DESCRIPTOR *ImgImpDescr;
UINT x = 0, y = 0;
CHAR *DllName;
ULONG_PTR *Thunks, *FThunks;
IMAGE_IMPORT_BY_NAME *ImgImpName;
_try
{
ImgDosHdr = (IMAGE_DOS_HEADER *) PE_Buffer;
ImgNtHdrs = (IMAGE_NT_HEADERS *) (ImgDosHdr->e_lfanew +
(ULONG_PTR) PE_Buffer);
if (ImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE ||
ImgNtHdrs->Signature != IMAGE_NT_SIGNATURE)
{
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
ImgBase = (ULONG_PTR) VirtualAllocEx(hProcess,
(PVOID) 0, //ImgNtHdrs->OptionalHeader.ImageBase,
ImgNtHdrs->OptionalHeader.SizeOfImage, MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
if (ImgBase == NULL)
{
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
//
// 文件的映像基址 == 内存映像基址吗?
// 不是则重定位
//
if (ImgNtHdrs->OptionalHeader.ImageBase != ImgBase)
{
Delta = ImgBase - ImgNtHdrs->OptionalHeader.ImageBase;
if (!ImgNtHdrs->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress ||
!(Reloc_Offset = RvaToOffset(ImgNtHdrs,
ImgNtHdrs->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress)))
{
VirtualFreeEx(hProcess, (LPVOID) ImgBase, 0, MEM_RELEASE);
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
ImgBaseReloc = (IMAGE_BASE_RELOCATION *)
(Reloc_Offset + (ULONG_PTR) PE_Buffer);
do
{
if (!ImgBaseReloc->SizeOfBlock)
break;
nItems = (ImgBaseReloc->SizeOfBlock -
IMAGE_SIZEOF_BASE_RELOCATION) / sizeof (WORD);
wData = (WORD *)(IMAGE_SIZEOF_BASE_RELOCATION +
(ULONG_PTR) ImgBaseReloc);
for (i = 0; i < nItems; i++)
{
Offset = (*wData & 0xFFF) + ImgBaseReloc->VirtualAddress;
Type = *wData >> 12;
if (Type != IMAGE_REL_BASED_ABSOLUTE)
{
BlockOffs = RvaToOffset(ImgNtHdrs, Offset);
if (BlockOffs == NULL)
{
VirtualFreeEx(hProcess, (LPVOID) ImgBase, 0, MEM_RELEASE);
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
Block = (DWORD *)(BlockOffs + (ULONG_PTR) PE_Buffer);
*Block += Delta;
}
wData++;
}
ImgBaseReloc = (PIMAGE_BASE_RELOCATION) wData;
} while (*(DWORD *) wData);
}
//
// 填充输入地址表
//
if (ImgNtHdrs->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress)
{
IT_Offset = RvaToOffset(ImgNtHdrs,
ImgNtHdrs->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
ImgImpDescr = (IMAGE_IMPORT_DESCRIPTOR *) (IT_Offset +
(ULONG_PTR) PE_Buffer);
// 对每一个描述符
while (ImgImpDescr[x].FirstThunk != 0)
{
DllName = (CHAR *) (RvaToOffset(ImgNtHdrs,
ImgImpDescr[x].Name) + (ULONG_PTR) PE_Buffer);
Thunks = (ULONG_PTR *) (RvaToOffset(ImgNtHdrs,
ImgImpDescr[x].OriginalFirstThunk != 0 ?
ImgImpDescr[x].OriginalFirstThunk :
ImgImpDescr[x].FirstThunk) + (ULONG_PTR) PE_Buffer);
FThunks = (ULONG_PTR *) (RvaToOffset(ImgNtHdrs,
ImgImpDescr[x].FirstThunk) + (ULONG_PTR) PE_Buffer);
y = 0;
//
// 模块中的每一个导入的函数
//
while (Thunks[y] != 0)
{
//
// 原始导入的吗?
//
if (Thunks[y] & IMAGE_ORDINAL_FLAG)
{
FThunks[y] = (ULONG_PTR) GetProcAddress(
GetModuleHandle(DllName),
(LPCSTR) (Thunks[y] - IMAGE_ORDINAL_FLAG));
y++;
continue;
}
ImgImpName = (IMAGE_IMPORT_BY_NAME *) (RvaToOffset(
ImgNtHdrs, Thunks[y]) + (ULONG_PTR) PE_Buffer);
FThunks[y] = (ULONG_PTR) GetProcAddress(GetModuleHandle(DllName),
(LPCSTR) &ImgImpName->Name);
y++;
}
x++;
}
}
}
_except (EXCEPTION_EXECUTE_HANDLER)
{
VirtualFreeEx(hProcess, (LPVOID) ImgBase, 0, MEM_RELEASE);
VirtualFree((LPVOID) PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
//
// 创建 PE 文件的虚拟映像
//
PE_Image = (BYTE *) VirtualAlloc(NULL,
ImgNtHdrs->OptionalHeader.SizeOfImage,
MEM_COMMIT, PAGE_READWRITE);
if (PE_Image == NULL)
{
VirtualFreeEx(hProcess, (LPVOID) ImgBase, 0, MEM_RELEASE);
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
ZeroMemory(PE_Image, ImgNtHdrs->OptionalHeader.SizeOfImage);
//
// 复制头部
//
IMAGE_SECTION_HEADER *Sect;
Sect = IMAGE_FIRST_SECTION(ImgNtHdrs);
RtlCopyMemory(PE_Image, PE_Buffer, Sect[0].PointerToRawData);
//
// 映射区段
//
for (UINT j = 0; j < ImgNtHdrs->FileHeader.NumberOfSections; j++)
{
BYTE *Source = (BYTE *)(Sect[j].PointerToRawData +
(ULONG_PTR) PE_Buffer);
BYTE *Dest = (BYTE *)(Sect[j].VirtualAddress +
(ULONG_PTR) PE_Image);
RtlCopyMemory(Dest, Source, Sect[j].SizeOfRawData);
}
BOOL bRet = WriteProcessMemory(hProcess, (LPVOID) ImgBase,
PE_Image, ImgNtHdrs->OptionalHeader.SizeOfImage, &BR);
if (!bRet)
VirtualFreeEx(hProcess, (LPVOID) ImgBase, 0, MEM_RELEASE);
VirtualFree(PE_Image, 0, MEM_RELEASE);
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
if (bRet == TRUE && BaseAddress != NULL)
*BaseAddress = ImgBase;
return bRet;
}
为了填充输入表我使用了远程的 getprocaddress 因为 dll 的输入表只是没有定位到系统的系统的 dll 上,所以一个本地的 getprocaddress 就已经足够了。下面的关联过程:
void CAntiMidaDlg::OnBnClickedAntimida()
{
ProcList.GetItemText(ProcList.GetNextItem(-1, LVNI_SELECTED), 1,
Buffer, sizeof (Buffer) -1);
ULONG_PTR PID = _tcstoul(Buffer, 0, 16);
wsprintf(Buffer, _T("%samdll.dll"), CurDir);
ULONG_PTR BaseAddress;
//
// 注入模块
//
if (InjectModule(PID, _T("amdll.dll"), &BaseAddress))
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL)
{
MessageBox(_T("Cannot make the process AntiMida"), "AntiMida");
return;
}
//
// 关联 ReadProcessMemory
//
ULONG_PTR Addr = (ULONG_PTR) GetProcAddress(
GetModuleHandle(_T("kernel32.dll")),
"ReadProcessMemory");
BYTE Instr = 0xB8;
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr,
sizeof (BYTE), NULL);
Addr++;
ULONG_PTR Rva = GetExportRva(Buffer, "_FakeReadProcessMemory@20");
ULONG_PTR HookVA = BaseAddress + Rva;
WriteProcessMemory(hProcess, (LPVOID) Addr, &HookVA,
sizeof (ULONG_PTR), NULL);
Addr += sizeof (ULONG_PTR);
WORD Instr2 = 0xE0FF;
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr2,
sizeof (WORD), NULL);
//
// 关联 VirtualQueryEx
//
Addr = (ULONG_PTR) GetProcAddress(
GetModuleHandle(_T("kernel32.dll")),
"VirtualQueryEx");
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr,
sizeof (BYTE), NULL);
Addr++;
Rva = GetExportRva(Buffer, "_FakeVirtualQueryEx@16");
HookVA = BaseAddress + Rva;
WriteProcessMemory(hProcess, (LPVOID) Addr, &HookVA,
sizeof (ULONG_PTR), NULL);
Addr += sizeof (ULONG_PTR);
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr2,
sizeof (WORD), NULL);
//
// 关联 WriteProcessMemory
//
Addr = (ULONG_PTR) GetProcAddress(
GetModuleHandle(_T("kernel32.dll")),
"WriteProcessMemory");
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr,
sizeof (BYTE), NULL);
Addr++;
Rva = GetExportRva(Buffer, "_FakeWriteProcessMemory@20");
HookVA = BaseAddress + Rva;
WriteProcessMemory(hProcess, (LPVOID) Addr, &HookVA,
sizeof (ULONG_PTR), NULL);
Addr += sizeof (ULONG_PTR);
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr2,
sizeof (WORD), NULL);
//
// 关联 NtAllocateVirtualMemory
// 从这里开始我就直接关联 ntdll
//
Addr = (ULONG_PTR) GetProcAddress(
GetModuleHandle(_T("ntdll.dll")),
"NtAllocateVirtualMemory");
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr,
sizeof (BYTE), NULL);
Addr++;
Rva = GetExportRva(Buffer, "_FakeNtAllocateVirtualMemory@24");
HookVA = BaseAddress + Rva;
WriteProcessMemory(hProcess, (LPVOID) Addr, &HookVA,
sizeof (ULONG_PTR), NULL);
Addr += sizeof (ULONG_PTR);
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr2,
sizeof (WORD), NULL);
//
// 关联 NtCreateThread
//
Addr = (ULONG_PTR) GetProcAddress(
GetModuleHandle(_T("ntdll.dll")),
"NtCreateThread");
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr,
sizeof (BYTE), NULL);
Addr++;
Rva = GetExportRva(Buffer, "_FakeNtCreateThread@32");
HookVA = BaseAddress + Rva;
WriteProcessMemory(hProcess, (LPVOID) Addr, &HookVA,
sizeof (ULONG_PTR), NULL);
Addr += sizeof (ULONG_PTR);
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr2,
sizeof (WORD), NULL);
//
// 关联 ZwDebugContinue
//
Addr = (ULONG_PTR) GetProcAddress(
GetModuleHandle(_T("ntdll.dll")),
"ZwDebugContinue");
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr,
sizeof (BYTE), NULL);
Addr++;
Rva = GetExportRva(Buffer, "_FakeZwDebugContinue@12");
HookVA = BaseAddress + Rva;
WriteProcessMemory(hProcess, (LPVOID) Addr, &HookVA,
sizeof (ULONG_PTR), NULL);
Addr += sizeof (ULONG_PTR);
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr2,
sizeof (WORD), NULL);
//
// 全都关联了
//
CloseHandle(hProcess);
MessageBox(_T("The process is now AntiMida"), "AntiMida");
}
else
{
MessageBox(_T("Cannot make the process AntiMida"), "AntiMida");
}
}
你是不是在那们为什么我关联了 kernel32 中起始的几个函数和 ntdll 中剩下的函数: 没什么原因。我只是在试试他能不能运行并且关联 kernel32 也没什么原因,然后我又懒得再去重写这些代码并关联了 ntdll (看见了吧,我真的是很懒的)。实际上最好的方法是直接关联 ntdll,因为他是最直接也是最快捷的(你会知道的)。GetExportRva 只是我写的一个勇于从输出表获取 RVA 的小函数: