显示标签为“Windows”的博文。显示所有博文
显示标签为“Windows”的博文。显示所有博文
5
Posted on 16:04:00 by Unknown and filed under ,

  之前曾经使用 VirtualBox 安装过 Ubuntu,现在因为测试需要,准备安装一下 Windows XP。

  结果一上来就遇到一个问题,在格式化一步出错,提示:Unable to allocate and lock memory...错误 ID:HostMemoryLow。原来是我把虚拟机的内存设置得太大了(设置的是 128M 好像,主机内存 1G+2G 页面缓存)。后来修改成 64M 就可以了。

  VirtutalBox 的虚拟硬盘是保存成 VDI 文件的,在虚拟系统运行的任何时刻,都可以生成一个当前系统运行状态的快照(Snapshots)。这个备份是保存在 Snapshots 目录下的一个新的 VDI 文件中,在快照生成之后的所有操作都是保存在这个新的 VDI 文件中,原始的文件就不会发生变化了。通过快速修复界面,可以恢复最近的快照。

  注意,当你新生成或者删除某一个快照备份的时候,原始的 VDI 就会发生变化,保存的是当前的系统状态。

  但是,VirtualBox 的快照恢复顺序类似与一个堆栈,只能恢复最近的快照,想要生成更早一点的状态,只能把最近的快照删除才行。这点也很好理解,因为如果想要并行保存两个状态,那势必要占用两份空间,而 VirtualBox 的快照是一个增量备份的过程,所以它只能线性恢复。

  而我们软件测试,是需要在同一个操作系统的不同软件环境下进行,比如需要保存刚刚安装完 Windows 的状态,还要保存已经安装常用软件的状态。这样就需要把虚拟硬盘的 VDI 文件多保存几份了。但是,直接复制 VDI 文件是不行的,因为 VirtualBox 对每个 VDI 文件有一个 uuid,所以直接复制以后无法注册,因为 uuid 是相同的。

  在帮助文件中给出了解决方案,就是通过命令行中使用 VBoxManage 的 clonevdi 的方法来复制一个 VDI 文件,并重新生成一个 uuid。使用方法如下,注意文件名如果有空格,需要用引号括起来。

VBoxManage clonevdi <uuid>|<filename> <outputfile>

  复制的过程相当慢...

  如何压缩虚拟硬盘的大小,在 VBoxManage 中有一个 modifyvdi compact 的命令,如果选用了动态分配磁盘,可以试试。注意到,因为在频繁的文件操作后,将会有大量的空间只被标记为删除,但是并没有真的清空。这样压缩就会没什么效果。

  一种方法是使用 Ghost 备份一下然后再恢复。另一种方法是:

  1. 首先进行磁盘碎片整理。
  2. 然后使用 Sysinternals 的一个命令行工具 SDelete,在 Guest OS 中执行:sdelete -c c:\ ,把C盘中标记为未使用的空间清0。
  3. 在 Host OS 中执行:VBoxManage modifyvdi "文件名.vdi" compact
1
Posted on 13:37:00 by Unknown and filed under , ,

  要编写一个类似于 Windows 任务管理器的软件,首先遇到的问题是如何实现枚举所有进程。暂且不考虑进入核心态去查隐藏进程一类的,下面提供几种方法。请注意每种方法的使用局限,比如使用这些 API 所需要的操作系统是什么(尤其是是否能在 Windows Mobile 下使用)。

  本文参考用户态枚举进程的几种方法,原文对于每一种方法都给出了完整的代码,被我照抄下来。还有一篇:如何用 Win32 APIs 枚举应用程序窗口和进程。基于我现学现卖的本质,对我演绎的部分请抱着批判的眼光来看,另外代码也没有充分验证。

使用 ToolHelp API

   ToolHelp API 的功能就是为了获取当前运行程序的信息,从而编写适合自己需要的工具(@MSDN)。它支持的平台比较广泛,可以在 Windows CE 下使用。在 Windows Mobile SDK 的 Samples 里面有一个 PViewCE 的样例程序,就是用这个来查看进程和线程信息的。

  使用方法就是先用 CreateToolhelp32Snapshot 将当前系统的进程、线程、DLL、堆的信息保存到一个缓冲区,这就是一个系统快照。如果你只是对进程信息感兴趣,那么只要包含 TH32CS_SNAPPROCESS 标志即可。

  然后调用一次 Process32First 函数,从快照中获取第一个进程,然后重复调用 Process32Next,直到函数返回 FALSE 为止。这样将遍历快照中进程列表。这两个函数都带两个参数,它们分别是快照句柄和一个 PROCESSENTRY32 结构。调用完 Process32First 或 Process32Next 之后,PROCESSENTRY32 中将包含系统中某个进程的关键信息。其中进程 ID 就存储在此结构的 th32ProcessID。此 ID 传给 OpenProcess API 可以获得该进程的句柄。对应的可执行文件名及其存放路径存放在 szExeFile 结构成员中。在该结构中还可以找到其它一些有用的信息。

  需要注意的是:在调用 Process32First() 之前,要将 PROCESSENTRY32 结构的 dwSize 成员设置成 sizeof(PROCESSENTRY32)。 然后再用 Process32First、Process32Next 来枚举进程。使用结束后要调用 CloseHandle 来释放保存的系统快照。

  以下为参考代码:

#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>

void useToolHelp()
{
    HANDLE procSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if(procSnap == INVALID_HANDLE_VALUE)
    {
        printf("CreateToolhelp32Snapshot failed, %d ",GetLastError());
        return;
    }
    //
    PROCESSENTRY32 procEntry = { 0 };
    procEntry.dwSize = sizeof(PROCESSENTRY32);
    BOOL bRet = Process32First(procSnap,&procEntry);
    while(bRet)
    {
        wprintf(L"PID: %d (%s) ", procEntry.th32ProcessID, procEntry.szExeFile);                                                  
        bRet = Process32Next(procSnap, &procEntry);
    }
    CloseHandle(procSnap);
}

void main()
{
    useToolHelp();
    getchar();
}

使用 Processing Status API

  在 Windows SDK 中可以找到 PSAPI,通过 PSAPI 可以获取进程列表和设备驱动列表。通过 EnumProcesses、EnumProcessModules、GetModuleFileNameEx 和 GetModuleBaseName 来实现。

  首先使用 EnumProcesses 来枚举所有进程,它有三个参数:DWORD 类型的数组指针 lpidProcess;该数组的大小尺寸 cb;以及一个指向 DWORD 的指针 cbNeeded,它接收返回数据的长度。DWORD 数组用于保存当前运行的进程 IDs。cbNeeded 返回数组所用的内存大小。下面算式可以得出返回了多少进程:nReturned = cbNeeded / sizeof(DWORD)。
  注意:虽然文档将返回的 DWORD 命名为“cbNeeded”,实际上是没有办法知道到底要传多大的数组的。EnumProcesses 根本不会在 cbNeeded 中返回一个大于 cb 参数传递的数组值。所以,唯一确保 EnumProcesses 函数成功的方法是分配一个 DWORD 数组,并且,如果返回的 cbNeeded 等于 cb,分配一个较大的数组,并不停地尝试直到 cbNeeded 小于 cb 。

  下面是参考代码:

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include "psapi.h"
#pragma comment(lib,"psapi.lib")


void PrintProcessNameAndID(DWORD processID)
{
    TCHAR szProcessName[MAX_PATH] = _T("<unknown>");
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS/* | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ*/,FALSE,processID);    
    //Process name.
    if(NULL!=hProcess)
    {
        HMODULE hMod;
        DWORD cbNeeded;
        if(EnumProcessModules(hProcess,&hMod,sizeof(hMod), &cbNeeded))        
        {
            GetModuleBaseName(hProcess,hMod,szProcessName,sizeof(szProcessName)/sizeof(TCHAR));                       
        }
    }
    wprintf(_T("PID: %d (%s) "),processID,szProcessName);
    CloseHandle(hProcess);
}

void main( )
{
    DWORD aProcesses[1024], cbNeeded, cProcesses;
    unsigned int i;
    if(!EnumProcesses(aProcesses,sizeof(aProcesses),&cbNeeded))
        return;
    cProcesses = cbNeeded/sizeof(DWORD);
    for(i=0;i<cProcesses;i++)
        PrintProcessNameAndID(aProcesses[i]);
    getchar();
}

  注意到,此方法由于需要进行 OpenProcess 操作,所以需要一定的权限,当权限不够时,有些进程将不能被打开。下面给出提升权限的相关代码:
void RaisePrivilege()
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    if(OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&hToken))
    {
        if(LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[0].Luid))
        {
            AdjustTokenPrivileges(hToken,FALSE,&tp,NULL,NULL,0);
        }
    }
    if(hToken)
        CloseHandle(hToken);
}



使用 Native API

  在 使用Native API 探测本机系统信息 中我介绍了 Native API 中的 NtQuerySystemInformation(ZwQuerySystemInformation)。当设置查询的信息类型为  SystemProcessesAndThreadsInformation 时(第5号功能),可以用来枚举所有进程和线程。

  提醒:这个函数属于 Undocumented API,并且不建议使用,因为不同系统的结构和常量有所不同。下面列出 Windows XP 下可以用的相关结构和常量:

typedef DWORD (WINAPI *ZWQUERYSYSTEMINFORMATION)(DWORD, PVOID, DWORD, PDWORD);

typedef struct _SYSTEM_PROCESS_INFORMATION
{
    DWORD NextEntryDelta;
    DWORD ThreadCount;
    DWORD Reserved1[6];
    FILETIME ftCreateTime;
    FILETIME ftUserTime;
    FILETIME ftKernelTime;
    UNICODE_STRING ProcessName;
    DWORD BasePriority;
    DWORD ProcessId;
    DWORD InheritedFromProcessId;
    DWORD HandleCount;
    DWORD Reserved2[2];
    DWORD VmCounters;
    DWORD dCommitCharge;
    PVOID ThreadInfos[1];
}SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

#define SystemProcessesAndThreadsInformation 5

  然后动态加载 ntdll.dll,获得函数的地址。便可以进行进程的枚举相关代码如下:
#include <windows.h>
#include <ntsecapi.h>
#include <stdio.h>

typedef DWORD (WINAPI *ZWQUERYSYSTEMINFORMATION)(DWORD, PVOID, DWORD, PDWORD);

typedef struct _SYSTEM_PROCESS_INFORMATION
{
    DWORD NextEntryDelta;
    DWORD ThreadCount;
    DWORD Reserved1[6];
    FILETIME ftCreateTime;
    FILETIME ftUserTime;
    FILETIME ftKernelTime;
    UNICODE_STRING ProcessName;
    DWORD BasePriority;
    DWORD ProcessId;
    DWORD InheritedFromProcessId;
    DWORD HandleCount;
    DWORD Reserved2[2];
    DWORD VmCounters;
    DWORD dCommitCharge;
    PVOID ThreadInfos[1];
}SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

#define SystemProcessesAndThreadsInformation 5

void main()
{
    HMODULE hNtDll = GetModuleHandle(L"ntdll.dll");
    if(!hNtDll)
        return;
    ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"ZwQuerySystemInformation");
    ULONG cbBuffer = 0x10000;
    LPVOID pBuffer = NULL;
    pBuffer = malloc(cbBuffer);
    if(pBuffer == NULL)
        return;
    ZwQuerySystemInformation(SystemProcessesAndThreadsInformation,pBuffer,cbBuffer,NULL);
    PSYSTEM_PROCESS_INFORMATION pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuffer;
    for(;;)
    {
        wprintf(L"PID: %d (%ls) ",pInfo->ProcessId,pInfo->ProcessName.Buffer);
        if(pInfo->NextEntryDelta == 0)
            break;
        pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo) + pInfo->NextEntryDelta);
    }
    free(pBuffer);
    getchar();
}
  
  对这个方法有问题的,可以参考我之前的那篇介绍 Native API 的文章。

  同样使用 ZwQuerySystemInformation 函数,查询类型如果设置为 SystemHandleInformation(第16号功能)也可以达到目的。它能获取系统中所有句柄,再加上进程 ID 的判断就可以枚举所有进程了。
#include <windows.h>
#include <ntsecapi.h>
#include <ntstatus.h>
#include <stdio.h>

typedef NTSTATUS (WINAPI *ZWQUERYSYSTEMINFORMATION)(DWORD, PVOID, DWORD, PDWORD);

typedef struct _SYSTEM_HANDLE_INFORMATION
{
    ULONG ProcessId;
    UCHAR ObjectTypeNumber;
    UCHAR Flags;
    USHORT Handle;
    PVOID Object;
    ACCESS_MASK GrantedAccess;
}SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

typedef struct _SYSTEM_HANDLE_INFORMATION_EX
{
    ULONG NumberOfHandles;
    SYSTEM_HANDLE_INFORMATION Information[1];
}SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX;

#define SystemHandleInformation 0x10   //16

void main()
{
    HMODULE hNtDll = LoadLibrary(L"ntdll.dll");
    if(!hNtDll)
        return;
    ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"ZwQuerySystemInformation");
    ULONG cbBuffer = 0x4000;
    LPVOID pBuffer = NULL;
    NTSTATUS s;
    do
    {
        pBuffer = malloc(cbBuffer);
        if(pBuffer == NULL)
            return;
        memset(pBuffer,0,cbBuffer);
        s = ZwQuerySystemInformation(SystemHandleInformation,pBuffer,cbBuffer,NULL);
        if(s == STATUS_INFO_LENGTH_MISMATCH)
        {
            free(pBuffer);
            cbBuffer = cbBuffer * 2;
        }
    }while(s == STATUS_INFO_LENGTH_MISMATCH);

    PSYSTEM_HANDLE_INFORMATION_EX pInfo = (PSYSTEM_HANDLE_INFORMATION_EX)pBuffer;
    ULONG OldPID = 0;
    for(DWORD i = 0;i<pInfo->NumberOfHandles;i++)
    {
        if(OldPID != pInfo->Information[i].ProcessId)
        {
            OldPID = pInfo->Information[i].ProcessId;
            wprintf(L"PID: %d ",OldPID);
        }
    }
    free(pBuffer);
    FreeLibrary(hNtDll);
    getchar();
}

  原文中提到,在进行进程“隐藏”工作的时候,此处的句柄是一件容易被忽略的地方,因此需要注意隐藏由程序打开的相关句柄。由于系统中句柄数量经常变换,所以没有什么必要修改其中的 NumberOfHandles 域,因为如果修改此处的值,则需要不停对句柄的变化进行维护,开销比较大。在用户态下的进程枚举已经变得不可靠,因为一个内核级的 Rootkit 很容易就能够更改这些函数的返回结果。所以进程的可靠枚举应在内核态中实现,可以通过编写驱动来实现。

有关16位程序


  根据参考的第二篇文章:在 Windows 95,Windows 98 和 Windows ME 中,ToolHelp32 对待16位程序一视同仁,它们与 Win32 程序一样有自己的进程 IDs。但是在 Windows NT,Windows 2000 或 Windows XP 中情况并不是这样。在这些操作系统中,16位程序运行在所谓的 VDM 当中(也就是DOS机)。
  为了在 Windows NT,Windows 2000 和 Windows XP 中枚举16位程序,必须使用一个名为 VDMEnumTaskWOWEx 的函数。它的声明包含在 Windows  SDK 中的 VDMDBG.h 中,并且需要在项目中链接 VDMDBG.lib 文件。
  微软的网上帮助里面有一篇介绍的文章:如何在 Windows NT、 Windows 2000 和 Windows XP 上使用 VDMDBG 函数
0
Posted on 17:15:00 by Unknown and filed under ,

  锁定屏幕的快捷方式是:Win + L,但是某些键盘没有 Windows 键,那如何方便的锁屏幕呢。

  一种方法是,打开开始菜单,选择注销,再选择切换用户。

  另外一种是,可以在运行中输入下面的命令,也可以建立一个批处理:

rundll32.exe user32.dll,LockWorkStation

0
Posted on 00:23:00 by Unknown and filed under ,

  一般我们实现系统功能都是通过调用微软提供的公用 Win32 API 函数来实现。但今天我们采用的方法是通过本机系统服务(Native API)来探测本机系统信息。

  Native API 是 Windows 用户模式中为上层 Win32 API 提供接口的本机系统服务。微软没有为我们提供关于本机系统服务的文档 (Undocumented),也就是不会为对它的使用提供任何的保证,所以不提倡使用 Native API 来开发软件。

  不过由于通过 Native API 可以实现很多功能,所以相关的参考也可以找到很多。 比如 Gary Nebbett, Windows NT/2000 Native API Reference. New Riders Publishing, 2000. 是一本详细的参考书(@Amazon ),内容可能不是最新的,但非常有价值。 而 The Undocumented Functions 是一个内容充足的在线参考手册。

  为了探测系统信息,我们要使用一个函数:NtQuerySystemInformation(与ZwQuerySystemInformation相同,只是入口不同),这是一个属于 ntdll.dll 的函数。

  在 MSDN 上对这个函数定义如下:

NTSTATUS WINAPI NtQuerySystemInformation(
  __in       SYSTEM_INFORMATION_CLASS SystemInformationClass,
  __inout    PVOID SystemInformation,
  __in       ULONG SystemInformationLength,
  __out_opt  PULONG ReturnLength
);

  难点在于各个数据结构是如何定义的。事实上,在不同版本的 Windows 中,这些数据结构的定义是有区别的,这也是微软不建议使用这些 API 的一个原因。

  通过我上面给出的资料倒是可以查到这些数据是如何定义的。比如 SYSTEM_INFORMATION_CLASS 是一个集合,如果我想要查看有关系统进程的信息,则要取 5 即 SystemProcessInformation。这个在 winternl.h 也有定义,但是这个头文件中的定义比较保守。更多的信息还需要自己 Google 才行。(比如上文提到的 The Undocumented Functions 中的 SYSTEM_PROCESS_INFORMATION

  对这个函数更详细的说明可以参见:NtQuerySystemInformation(ZwQuerySystemInformation)函数说明,这篇文章也有很全的 SYSTEM_INFORMATION_CLASS 是如何定义的。以及我最早参考的NtQuerySystemInformation,也讲了大部分数据结构如何定义。

  至于如果调用这个函数在分析完数据结构后就比较简单了。下面是一个简单的示例,里面还有很多没有说明的类型,不影响理解,进一步查看相关资料就能清楚了。

// 定义类型 
typedef NTSTATUS (WINAPI *PROCNTQSI)(UINT,PVOID,ULONG,PULONG); 
PROCNTQSI NtQuerySystemInformation;

// 调用 
NtQuerySystemInformation = (PROCNTQSI)GetProcAddress( 
GetModuleHandle("ntdll"), 
"NtQuerySystemInformation" 
);

  网上有很多例子可以参考,第一个是获取每个进程和线程:Processes and Threads Sample,讲解详细(网页上有一个小笔误),提供代码下载,用到了 Windows NT DDK,但是如果用上面我说的方法调用的话就不需要了 DDK 了。研究明白了这个,要点就全掌握了。

  还有一个是 Windows NT/2000系统中如何获取系统的启动时间,也有代码下载。

  上面提到的 GetProcAddress,GetModuleHandle,要涉及到动态链接库的使用。针对性的,可以参考:3个脱壳相关的重要函数介绍。简单地说就是:

  • 如果文件已经被映射进了调用进程的地址空间,则函数 GetModuleHandle 返回指定模块的句柄。注意,GetModuleHandle 是在不增加引用次数的情况下返回已映射模块的句柄。
  • LoadLibrary 把模块映射进调用进程的地址空间内。映射进地址空间后,如果必要,则增加模块的引用次数。最后用 FreeLibrary 把它从进程地址空间释放掉。
  • 使用 GetProcAddress 函数返回指定的输出动态链接库内的函数地址。