标签 逆向 下的文章

熊猫烧香病毒分析及思维导图

xmind.png

0x00 基本介绍

熊猫烧香是一个感染性的蠕虫病毒,它能感染系统中的 exe ,com ,pif,src,html,asp 等文件,它还能中止大量的反病毒软件进程并且会删除扩展名为 gho 的文件,该文件是一系统备份工具 GHOST 的备份文件,使用户的系统备份文件丢失,被感染的用户系统中所有的.exe可执行文件图标全部被改成熊猫烧香的图标。

分析一个病毒有许多的步骤,本文仅局限于逆向分析层面,如查壳,行为分析,运行效果分析等未在本文展开,文末会放上个人认为比较好的分析文章以供学习。

0x01 环境及工具

分析环境:VMware Workstation Pro 15 + Win7 x86

工具:IDA Pro 7.0 & OD

0x02 Delphi的特性

1.函数用寄存器传参

2.【字符串地址 - 0x04】为字符串长度

0x03 逆向分析

1. 字符串比较

以“xboy”和“whboy”为key 对字符串进行解密,并与程序内原有字符串进行比较,比较成功进入三大核心模块

image-20200913140029931

2. CreateAndRunPanda模块

2.1 删除Desktop_.ini文件

Desktop_.ini.png

2.2 对文件本体进行判断

image-20200913165912948

2.3 如果为原始程序,则继续执行

先判断进程内是否存在spcolsv.exe,存在就结束进程。

image-20200913170526411

把病毒源程序复制到C:\Windows\System32\drivers\spcolsv.exe,然后启动spcolsv.exe(伪装),关闭自身源程序。

伪装程序和原本的panda.exe 内容是一模一样的,唯一不同就是在此前判断是否为spcolsv.exe程序的地方。

image-20200913170603049

2.4 如果本体为spcolsv.exe(伪装程序) 则跳转至loc_4085BA,释放之前申请存放的病毒文件信息的内存。

image-20200913171855862

1end.png

2.5 被感染后的程序末尾有五个字节都是Whboy且末尾是数字,通过文件中感染标记来区分,目前运行的是病毒本体还是被感染的文件如果是被感染文件会跳转至loc_4085D3,判断spclosv.exe是否存在,如果不存在从被感染的文件分裂出病毒程序重新执行。

感染后.png

3. InfectOtherFile模块

3.1 调用三个功能函数,三个函数大致的功能是感染本地文件,创建autorun.inf和setup.exe,利用139和445端口局域网传播。

threefunc.png

3.2 创建新线程通过查找到的盘符,遍历盘符中的各种文件并进行感染,同时规避掉一些系核心目录

VirusThread.png

3.3 sub_4094A4函数判断了是否为文件程序,诺为程序则跳转至感染区块

Infect.png

感染区块先删除GHO文件,断了被感染者备份的路。

delgho.png

判断文件后缀,诺为 EXE,RCS,FIP,MOC文件则执行一般感染函数

InfectFile.png

诺文件后缀为html,asp等网页文件格式进行网页感染函数,网页感染函数只是在网页文件尾部写入“

WebInfect.png

3.4 诺为文件夹目录,则先规避掉系统核心目录,在目录下写入隐藏的Desktop_.ini文件,并判断

writeini.png

在c:\test.txt中写入感染信息

writetxt.png

3.5 感染完文件后,代码会执行到设置计时器函数,这里定时调用TimerFunc函数、在这个函数中,会生成自启动autorun.inf以及病毒复制体setup.exe,工作原理与此前分析的生成伪装体和生成Desktop_.ini类似。

TimeFunc.png

3.6 局域网传播函数,先是创建线程回调函数,进入sub_40BCC8->sub_40BAA0 函数,进入此函数就有点类似网络编程了,先初始化套接字,创建套接字,尝试连接,关闭套接字。

threadhuidiao.png

Socket.png

最后如果尝试连接端口成功则把病毒拷贝到网络文件夹

CopyNetFileWork.png

4. VirusProtectSelf模块

4.1 四个时钟回调函数

TimerFunc.jpg

4.2 第一个回调函数(sub_40CD30)

通过发送窗口关闭消息关闭反病毒软件和任务管理器,同时通过注册表设置开机自启动和文件隐藏。

1timer.png

4.3 第二个回调函数(sub_40CE8C)

定期从 http://wangma.9966.org/down.txt 读取源码并下载文件并执行

2timer.png

4.4 第三个回调函数(sub_40CE94)

创建俩个线程,第一个线程和第二个回调线程(sub_40CE8C)执行内容一致

3timer.png

第二个线程删除网络共享文件,并且设置用户文件隐藏共享。

3.2timer.png

4.5 第四个回调函数(sub_407540)

删除服务、删除安全软件相关启动项

!4timer.png

0x04 总结

1. 特征地址及特征字符

http://wangma.9966.org/down.txt

WhBoy, 武*汉*男*生*感*染*下*载*者*

2. 优秀文章

病毒分析学习之旅--熊猫烧香

熊猫烧香病毒逆向过程及其分析思路

0x00 前言

本文是为了记载一些自己学习过程中发现的IDA使用小技巧,以便以后再次回想和复习,每个tip下面都会有诺干个链接以便拓展学习。

0x01 正文

  1. C键可以让ida再次分析程序,即实现从数据转化为代码的过程。
    [[求助]看《加密与解密》的一些疑问](https://bbs.pediy.com/thread-29374.htm) IDA-数据与代码、函数互相转换
  2. 堆栈不平衡,无法F5大法。可以先在option->general,把Stack pointer打开,然后选中最近的call,alt+k修改堆栈(把值改为函数最后的负数)。
    BUUCTF re:Youngter-drive
  3. IDA里面的反调试特征,__readgsqword(0x60)得到指向PEB64的指针,__readfsdword(0x30)得到指向PEB32的指针,TEB(0x30)=PEB。
    反调试
  4. 数据段明显为字符串,但IDA未识别出来,可使用快捷键A转换为字符串

5.【CTF】HWS计划2020安全精英夏令营培训 这个课程挺好的,基本上基础都过了一遍

6.在调试程序的时候有动态基址,可以IDA -> Edit -> Segments -> Rebase program修改IDA内的默认基址

首届四川省安恒杯CTF逆向WP

0x01 gostright

题目地址:https://jev0n.lanzous.com/ioijle9jcti

0.0 大致思路

开屏暴击,说我缺少一个DLL文件。

00.jpg
我先是没去管它直接拖到IDA里面去看一下,习惯性shift+f12看一下字符串,发现了换表的Base64。

01.jpg
用CyberChef操作了一下,感觉应该是对付反调试的。

02.jpg
然后往下一点就发现了一个'congratulation!'的字符串按X交叉引用过去,F5大法,看起来像是个迷宫之类的题目,这个时候的关键就是要找到这个迷宫。

03.jpg
接着对函数交叉引用看看是谁调用了这个checkflag,在交叉引用过程中发现出现了sp错误,这个时候就要去修复一下位置为0x402EB4

04.jpg
在Options->General处打开Stack pointer,然后发现这里有为负值,用Alt+K修复一下,然后就可以F5了

05.jpg
初步判断这个就是对迷宫进行构造的地方,最主要的就是那个两层循环

06.jpg
然后就是构造迷宫

maze.jpg
最后结合起初发现的那个包含成功提示字符串的函数,进行走迷宫,flag的body一共24个字符,每两个为一组,第一个是方向,第二个是走多少步(用大写字母顺序来判断如:B为走2步)

0.1 迷宫代码

#include<iostream>
using namespace std;

int main()
{
    int array[] = {42,43,40,41,46,47,44,45,34,35,32,33,38,39,36,37,58,59,56,
                    43,40,41,46,47,44,45,34,35,32,33,38,39,36,37,58,59,97,57,
                    40,41,46,47,44,45,34,35,32,33,38,39,36,37,58,59,56,56,62,
                    41,47,46,45,44,35,34,33,32,39,38,37,36,59,58,57,56,63,63,
                    46,46,44,45,34,35,32,33,38,39,36,37,58,59,56,57,62,63,60,
                    47,45,44,35,34,33,32,39,38,37,36,59,58,57,56,63,62,61,61,
                    44,45,34,35,32,33,38,39,36,37,58,59,56,57,62,63,60,60,50,
                    45,35,34,33,32,39,38,37,36,59,58,57,56,63,62,61,61,51,51,
                    34,34,32,33,38,39,36,37,58,59,56,57,62,63,60,60,50,50,48,
                    35,33,33,39,38,37,36,59,58,57,56,63,62,112,61,51,51,49,49,
                    32,32,38,38,36,37,58,59,56,57,62,63,60,61,50,50,48,48,54,
                    33,39,39,37,36,59,58,57,56,63,62,61,60,51,50,49,49,55,55,
                    38,38,36,37,58,59,56,57,62,63,60,61,50,51,48,49,54,54,52,
                    39,37,36,59,58,57,56,63,62,61,60,51,50,49,48,55,54,53,53,
                    36,37,58,59,56,57,62,63,60,61,50,51,48,49,54,55,52,53,10};
    int i = 0;
    for (i = 0; i < 285; ++i)
    {
        int k = i / 19 + i % 19;
        if (i % 19 == 0 && i != 0)
        {
            cout << endl;
        }
                cout << (char)(array[i] ^ k);
    }

    getchar();
    return 0;
}

0.2 总结

题目逻辑上手之后感觉还可以,起初自己比较懒加上太久没做题,总想着找空子找捷径结果就是没在规定时间内写完有点亏,然后还有就是因为我机子上缺个DLL所以也就没进行动态调试,我看网上的方法也不得行,不懂为什么。

0x01 环境

系统:Windows 10
编译环境:MSVC x86
关键函数:QueueUserAPC 函数

0x02 线程 APC 队列简介

就是在正常函数之外的另外一个函数,类似于SEH在进程遇到异常的地位,默认情况下,创建线程时不会创建这个队列,当调用 QueueUserAPC函数时,就会为这个线程创建这个队列。创建 APC 队列的函数,一般使用 Wait 函数族或者 SleepEx 函数等带有 bAlertable 参数的函数进入一种假"警惕"的状态, 进入 Alertable 状态的线程,系统调度器会在线程函数本身处于"警惕"(暂停等待状态)时,调用线程 APC 队列中的函数。

0x03 原理及步骤

1.原理

在一个进程中,执行到SleepEx()或WaitForSingleObjectEx()时,系统就会产生一个“中断”,当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。我们就可以利用QueueUserAPC()这个API,以此去执行我们设定的代码,进而完成DLL注入的目的。

2.步骤

0.通过目标窗口名获取PID
1.利用快照枚举所有的线程找到与PID相等的TID
2.开辟远程内存空间,写入Dll的路径
3.插入我们的DLL即可

0x04 代码

#include <Windows.h>
#include <Tlhelp32.h>
#include <iostream>

// 用于保存线程 TID 的指针
ULONG *pThreadId = (ULONG * )malloc(sizeof(ULONG));

// 根据进程名称获取 PID
// ProcessName 进程名称
ULONG GetProcessIdByProcessName(char* ProcessName)
{
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    //拍快照
    if (INVALID_HANDLE_VALUE == hSnapshot)
    {
        return 0;
    }

    PROCESSENTRY32 pi;
    pi.dwSize = sizeof(PROCESSENTRY32); //初始化成员
    BOOL bRet = Process32First(hSnapshot, &pi);
    while (bRet)
    {
        char Name[MAX_PATH] = { 0 };
        sprintf_s(Name, "%ls", pi.szExeFile);
        if (strcmp(Name, ProcessName) == 0)
        {
            return pi.th32ProcessID; // 返回PID
        }

        bRet = Process32Next(hSnapshot, &pi);
    }

    return 0;
}

// 根据 PID 获取获取相应的线程 TID
// ProcessId    进程 PID
// pThreadId   数组用于保存线程TID
// ThreadIdLen 用于返回数组实际长度
BOOL GetThreadIdByProcessId(ULONG ProcessId, ULONG* pThreadId, ULONG* ThreadIdLen)
{
    HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
    THREADENTRY32 te32 = { sizeof(THREADENTRY32) };
    ULONG Number = 0;

    // 把所有线程拍一个快照
    hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (hThreadSnap == INVALID_HANDLE_VALUE)
        return(FALSE);

    // 在使用 Thread32First 前初始化 THREADENTRY32 的结构大小.
    te32.dwSize = sizeof(THREADENTRY32);

    // 现在获取系统线程列表, 并显示与指定进程相关的每个线程的信息
    do {
        // 比对是否为该进程线程
        if (te32.th32OwnerProcessID == ProcessId)
        {
            // 是的话保存到线程数组中
            *pThreadId = te32.th32ThreadID;
            break;
        }
    } while (Thread32Next(hThreadSnap, &te32));

    if (!Number)
        return FALSE;

    return TRUE;
}

// APC注入
// ProcessName 要注入的进程名称
// DllName     要注入的DLL名称
BOOL ApcInjectDll(char* ProcessName, char* DllName)
{
    ULONG ProcessId = 0;
    ULONG IsFlag = TRUE;
    HANDLE hProcess = NULL, hThread = NULL;
    ULONG DllNameLen = strlen(DllName) + 1;
    PVOID pBaseAddr = NULL;
    ULONG dwRet = 0;
    PVOID pLoadLibraryA = NULL;
    ULONG ThreadIdsLen = 0;

    do
    {
        // 根据进程名称获取 PID
        ProcessId = GetProcessIdByProcessName(ProcessName);
        if (0 >= ProcessId)
        {
            printf("GetProcessIdByProcessName Error: %d\n", GetLastError());
            IsFlag = FALSE;
            break;
        }
        // 根据 PID 获取获取所有相应的线程 TID
        IsFlag = GetThreadIdByProcessId(ProcessId, pThreadId, &ThreadIdsLen);
        if (!IsFlag)
        {
            printf("GetAllThreadIdByProcessId Error: %x\n", GetLastError());
            IsFlag = FALSE;
            break;
        }
        // 打开注入线程
        hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
        if (hProcess == NULL)
        {

            printf("OpenProcess Error: %x\n", GetLastError());
            IsFlag = FALSE;
            break;
        }
        // 在注入的进程中申请空间
        pBaseAddr = VirtualAllocEx(hProcess, NULL, DllNameLen, MEM_COMMIT | MEM_RESERVE,
                PAGE_EXECUTE_READWRITE);
        if (pBaseAddr == NULL)
        {
            printf("VirtualAllocEx Error: %x\n", GetLastError());
            IsFlag = FALSE;
            break;
        }
        // 向申请的空间中写入 Dll 路径数据
        WriteProcessMemory(hProcess, pBaseAddr, DllName, DllNameLen, &dwRet);
        if (DllNameLen != dwRet)
        {
            printf("WriteProcessMemory Error: %x\n", GetLastError());
            IsFlag = FALSE;
            break;
        }

        hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, *pThreadId);
        if (hThread != NULL)
        {
            // 插入 APC
            if (!QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)pBaseAddr))
            {
                printf("QueueUserAPC Error: %x\n", GetLastError());
            }
            // 关闭线程句柄
            CloseHandle(hThread);
            hThread = NULL;
            printf("插入 APC 成功!\n");
        }

    } while (FALSE);

    CloseHandle(hProcess);
    return IsFlag;
}

int main()
{
    // 注入我们想要执行的DLL
    ApcInjectDll((char*)"TestExe.exe", (char*)"./Dll1.dll");
    system("pause");
    return 0;
}

0x05 总结及相关文章

总的来说APC注入对目标进程是有要求的,需要对方进入“警惕”状态下的,文章中的代码仅向目标进程的一个线程进行注入,如果有需要可以添加提权和枚举所有线程的操作。其次也在其他网站上看到APC队列的由来好像是和驱动文件有关系,所以在内核层面应该会有不一样的APC注入。
学习文章:
UserMode下的APC插入
APC异步过程调用
常见注入手法第二讲,APC注入(注意此方法中的示范代码并不能成功注入,个人认为是因为MFC消息机制等原因,原理是相似的)

0x00前言

很惭愧,在刚放寒假立下的FLAG现在才来实践,不过好在假期还没结束还有时间,由于在学校的时候有接触过一点所以就直接从第5节开始了,b站链接在这里,本文的目的也是为了自己更好的学习,所以不会写的很详细只会写一些自己认为比较有用且需要重点记忆的内容,,学长推荐的教材是《Windows程序设计(第5版 珍藏版)》,有机会我希望能够仔细拜读一下。我也是个初学者,如有任何错误,敬请各位读者斧正。

0x01进程与进程创建(5-6)

1,进程与线程关系:首先举个例子,进程就可以比喻成房屋,线程可以比喻成房屋里的人,人可以做到我们想要的事情,房屋(进程)可以给人(线程)提供场所(空间)和装备(资源),想要做成一件事情就一定要有人去完成(一个进程里至少要有一个线程),当然要做大事一个人力量是不够的,这个时候就可以叫兄弟帮忙了(多线程),在一个房屋内一起写项目(多个线程共用进程资源与空间)。这个例子简单的讲述了进程与线程的关系,虽然有可能有问题的地方,不过还是比较通俗易懂了。
2,进程内存空间地址分配:首先看张图1.jpg从这张图中可以很直观的看出一个进程在内存空间里面有着4GB的虚拟内存空间,不过这4GB并不全部都是拿来放本进程可以拿来使用的,我们可以分为高2G和低2G,高2G是内核区域,内核区域所有进程是一模一样的。所以在不同进程之间主要有区别的是在用户模式区。! 这里 !有着比较详细的资料后期必仔细拜读。
3,(1)进程的创建:任何的进程都是由进程创建的,我们在桌面上双击程序所创建的进程都是由(explorer.exe)这个进程创建的,而explorer.exe这个进程是由系统创建的。 (2)创建过程:我们还是直接看图来的更直观一点主要是懒2.jpg 在第五步映射DLL之前有一个傀儡进程的操作最好去原视频看看(32:02),具体实现原理等后期学到在补坑。
4,CreateProcess()函数:这个函数和LoadLibrary()一样是有区分不同的编码方式。(其实大多数的win32API都有这个设定)

#ifdef UNICODE
#define CreateProcess  CreateProcessW
#else
#define CreateProcess  CreateProcessA
#endif // !UNICODE

以下是这个函数的定义

BOOL
WINAPI
CreateProcessW(
    _In_opt_ LPCWSTR lpApplicationName,
    _Inout_opt_ LPWSTR lpCommandLine,
    _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ BOOL bInheritHandles,
    _In_ DWORD dwCreationFlags,
    _In_opt_ LPVOID lpEnvironment,
    _In_opt_ LPCWSTR lpCurrentDirectory,
    _In_ LPSTARTUPINFOW lpStartupInfo,
    _Out_ LPPROCESS_INFORMATION lpProcessInformation
    );

不要看上面定义的参数那么多,目前我们只需要了解前俩个和后俩个参数,下面让我们慢慢学习。先说前俩个参数,他们要接受的都是TCHAR类型的数组,唯一的区别是lpApplicationName要的是不可变(const)参数,而lpCommandLine需要的是可变参数。对于后俩个参数,lpStartupInfo所要接受的是一个数组代码如下

typedef struct _STARTUPINFOW {
    DWORD   cb;
    LPWSTR  lpReserved;
    LPWSTR  lpDesktop;
    LPWSTR  lpTitle;
    DWORD   dwX;
    DWORD   dwY;
    DWORD   dwXSize;
    DWORD   dwYSize;
    DWORD   dwXCountChars;
    DWORD   dwYCountChars;
    DWORD   dwFillAttribute;
    DWORD   dwFlags;
    WORD    wShowWindow;
    WORD    cbReserved2;
    LPBYTE  lpReserved2;
    HANDLE  hStdInput;
    HANDLE  hStdOutput;
    HANDLE  hStdError;
} STARTUPINFOW, *LPSTARTUPINFOW;
````

这个数组主要的信息是父进程给所产生的新进程写入的信息,不过修改次数组内容并不会对新进程产生影响。最后一个参数也是传入一个数组代码如下

typedef struct _PROCESS_INFORMATION {

HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;

} PROCESS_INFORMATION, PPROCESS_INFORMATION, LPPROCESS_INFORMATION;


这个数组里是线程和进程的ID及句柄,具体什么是句柄下次再说。  
**最后有个反调试的操作**,就是对lpStartupInfo数组内容进行判别,直接双击打开和通过OD等调试器打开里面初始化的参数内容是不一样的,就此可以进行简单的反调试。

## 0x02句柄表(7-8)

1,内核对象:首先我们来了解一下什么是内核对象,像进程、线程、文件、互斥体、事件等在内核都有一个对应的结构体,这些结构体由内核负责管理。我们管这样的对象叫做内核对象。我们可以看CreateXXX函数的定义,其中有类型为**LPSECURITY_ATTRIBUTES**的参数,则这个函数创建的对象为内核对象。  
2,句柄表:***只有每个进程有一个句柄表***。设置句柄表是为了避免使用地址来访问内核对象,因为涉及R0的东西一旦出现地址出差就会没有访问权限从而导致电脑蓝屏,句柄就很好的隔离开内核层与用户层。句柄表通过进程的内核结构体中的一个指针指向。
3,句柄表的继承:用我的话来说就是双重保险,第一层是创建内核对象时在LPSECURITY_ATTRIBUTES里有一个参数可以选择**本内核对象**是否允许继承。第二层是在创建子进程时使用CreateProcess()函数时第五个参数也可以选择是否继承**句柄表**。  
4,句柄值:因为我们之前称的句柄表都是进程中私有的,仅在本进程可以使用,这样就照成了很多麻烦,所以就出现了全局句柄表的值(句柄值)也就是我们常说的PID。通过PID就可以实现在不同进程中互相调用的效果。
5,工作路径和模块路径:模块路径就是我们传统意义上EXE所在的位置的整个路径,而工作路径就是由父进程所编写的路径。工作路径的作用就是在工作路径内的文件想要访问或打开,可以不需要写完整路径,只要写文件名和拓展名即可。  

## 0x03 线程(9-12)

1,创建线程:使用CreateThread()函数创建线程
2,线程控制函数:Sleep()程序执行到此次暂停多少秒(让自己停下来);SuspendThread()传入线程句柄可以使该线程挂起暂停(让别人停下来)与之对应的就是ResumeThread()恢复线程状态使之继续进行。  
3,线程状态等待:WaitForSingleObject()等待单个线程结束(INFINITE)或者在某一个时间后接着进行【WaitForMultipleObjects()和这个类似就是可以等待多个线程】;GetExitCodeThread() 线程一段代码结束后会有一个返回值可以用这个函数获取,可以根据自己的需求进行修改返回值。  
4,多线程操作中不同线程间寄存器数值的存放函数 使用SetThreadContext()修改 和 GetThreadContext()进行读取。[Dll注入新姿势:SetThreadContext注入](https://www.anquanke.com/post/id/86786)   
5,多线程安全问题:前提条件:1.有全局变量,2.对全局变量进行读操作。  
解决办法:临界区(对临界资源(全局变量)进行访问的代码段)通过线程锁 EnterCriticalSection(**令牌地址**) 和 LeaveCriticalSection(**令牌地址**)这俩个函数之间的代码就是临界区,令牌记得需要用InitializeCriticalSection(**令牌地址**)初始化。  
6,互斥体:内核级的令牌,在不同进程的线程中起作用;互斥体起作用的条件(任意一个即可):有信号或者线程的拥有者。可以通过创建互斥体函数(CreateMutex())的返回值用来放置多开程序。  

## 0x04 事件(13)

1,通知类型:在使用CreateEvent()的对第二个参数进行设定可以选择是用通知模式还是互斥模式(此次互斥模式和上文的互斥体类似,简单的来说就是*有我没你,有你没我*)。通知模式下WaitForSingleObject()之类线程等待函数不会改变事件状态(信号有无),简单的说就是可以通知多个线程。  
2,线程同步:同步的前提是互斥,互斥是无序的(某个线程连着好几次使用同一个临界资源)。互斥 + 有序 = 同步。方法(1)可以使用互斥体不过会浪费CPU时间片造成资源浪费。方法(2)使用俩个事件进行相互通讯,SetEvent(另外一个事件)挂起自己通知别人。  

## 0x05 窗口的本质(14)

1,HWND与HANDLE的区别:HWND是HANDLE的一种,但是HANDLE并不是HWND,HANDLE是操作系统一系列内核对象的句柄,HWND仅是窗口对象的句柄。  

2,绘制窗口的步骤:(1)设置设备对象(即在哪画,NULL默认是桌面)。  
(2)获取设备对象上下文(HDC),因为我们都是先画到设备对象的内存里所以需要中国设备对象上下文。  
(3)创建图形对象,可以使用GetStockObject()函数获取系统默认图形对象。  
(4)关联上下文和图形对象SelectObject()。  
(5)开始画(使用LineTo(),Rectangle()等函数,起始点默认为(0,0),可以用MoveToEx()修改画线初始点)。  
(6)释放资源(一般Create类函数用DeleteObject()类函数,Get类函数用ReleaseDC()类函数)。

## 0x05 消息机制(15)

1,每个线程只有一个消息队列,消息队列是存放在R0层线程对象的结构体里的。  
2,鼠标的消息如何到达消息队列:首先在R0层每个窗口都有一个窗口对象,这个窗口对象(WinObject)里面记录了窗口的起始位置,大小,层级关系,所在线程等等;其次鼠标点击后会把鼠标所在位置,左键还是右键等打包成一个结构体(消息);最后通过位置找到消息所属的窗口对象指向的线程加入到消息队列里。  
3,每个线程可以有多个窗口,一个窗口只能属于一个线程。

## 0x06 结束