• 我们在哪一颗星上见过 ,以至如此相互思念 ;我们在哪一颗星上相互思念过,以至如此相互深爱
  • 我们在哪一颗星上分别 ,以至如此相互辉映 ;我们在哪一颗星上入睡 ,以至如此唤醒黎明
  • 认识世界 克服困难 洞悉所有 贴近生活 寻找珍爱 感受彼此

IDA系列教程:由软件破解谈windows消息机制

IDA系列教程 云涯 4年前 (2020-04-03) 2120次浏览

0x01 背景

在IDA系列教程(一)中附件中有一个可执行程序,在分析这个程序时,部分函数涉及到windows消息机制,恶意代码也会利用windows消息机制来隐藏恶意功能代码。

0x2 基础知识

以下知识选自安全客文章《Windows调试艺术——从真实病毒学习消息机制》一文,本文章尊重原著,纯属学习目的。

Windows是一个消息驱动的系统。Windows的应用内部的各个线程、各个应用、应用与操作系统之间都会通过消息来传递。消息就是一个信号,应用会根据收到的信号做出不同的反应,比如我们点击了窗口的关闭按钮,那么就会传递给应用一个”关闭”的消息,然后窗体关闭。Windows以窗口作为基础实现了可视化的交互,窗口是基于线程实现的,一个线程又维护着一个消息队列,每一个传递给这个窗口的消息都要依次进入队列进行”先进先出”的操作,不分轻重缓急,再紧急的情况也只能老老实实排队。

消息

一个消息说白了就是一段数据,消息在Windows的定义如下

typedef struct tagMsg
{
HWND hwnd;    //目标的窗口句柄
UINT message; //消息的标识符
WPARAM wParam;//附加信息,与消息标识符有关
LPARAM lParam;//附加信息,与消息标识符有关
DWORD time;   //消息产生的时间
POINT pt;     //消息发生产生时的按屏幕坐标表示的鼠标光标的位置
}MSG,*PMSG;

消息按照用途可以分为:

  • 窗口消息,比如WM_PAINT窗口绘制、WM_CREATE窗口创建等等
  • 命令消息,一般是指WM_COMMAND,表示用户执行了一个命令,产生的对象一般是菜单或是控件
  • 通知消息,一般是指WM_NOTIFY,由公用控件发出
  • 反射消息,处理需要经过”反射”机制的消息,之后会详细说明

消息按照区段可分为:

  • 标识符由0x0000到0x03ff的系统消息
 0x0001-0x0087    窗口消息。
 0x00A0-0x00A9    非客户区消息 
 0x0100-0x0108    键盘消息
 0x0111-0x0126    菜单消息
 0x0132-0x0138    颜色控制消息
 0x0200-0x020A    鼠标消息
 0x0211-0x0213    菜单循环消息
 0x0220-0x0230    多文档消息
 0x03E0-0x03E8    DDE消息
  • 标识符由0x0400到0x7FFF的用户自定义消息,以VM_USER(0x0400)为基址,自定义偏移所对应的消息
  • 标识符由0x8000到0xBFFF的用户自定语消息,一般是基于某一个窗口类。用作应用之间的通信
  • 标识符由0xC000到0xFFFF的来自于RegisterWindowMessage函数,它会将传入的字符串注册成一个信息

消息队列

Windows维护了两种类型的队列,一种是系统消息队列,它是唯一的,用户的输入通过驱动程序转化为消息后会进入该队列,然后再将消息放入对应线程(窗口)的消息队列;

另外一种是线程消息队列,在调用User或者GDI的函数时创建,队列中的消息会经过消息泵传递给窗口回调函数。

消息也不都是这么”听话”,比如一下的几种

  • WM_PAINT、WM_TIMER等,它们只有在队列中没有其他消息的时候才会处理,而VM_PAINT甚至还会进行合并来提高效率,这其实是因为它们消息的优先级较低
  • WM_ACTIVATE、WM_SETFOCUS等,它们会绕过消息队列直接被目标窗口处理
  • 来自其他线程的消息,处理上还是一样,但是它们的优先级较高一些,在下边消息处理中会有所体现

消息的处理过程

消息首先由系统或应用产生,由于应用的消息可定制化程度太高,所以我们这里选择系统的消息来作为例子。

消息的传递对应大体有两种方式,一种是POST,一种是SEND,涉及到了各种各样的发送形式

postMessage //消息进入消息队列中后立即返回,消息可能不被处理。

PostThreadMessage //消息放入指定线程的消息队列中后立即返回,消息可能不被处理。

SendMessage //消息进入消息队列中,处理后才返回,如果消息不被处理,发送消息的线程将一直处于阻塞状态,等待消息返回。

SendNotifyMessage//如果消息进入本线程,则为SendMessage(),不是则采取postMessage(),当目标线程仍然依send处理 SendMessageTimeout //消息进入消息队列,处理或超时则返回,实际上SendMessage()就是建立在该函数上的 SendMessageCallback //在本线程再指定一个回调函数,当处理完后再次处理 BroadcastSystemMessage //发送目标为系统组件,比如驱动程序

消息发送处理时会先判定消息的目标是不是在同一线程而产生不同的结果

  • 是,SendMessage()发送的消息不进入消息队列直接处理,而postMessage()进入消息队列
  • 否,SendMessage()发送消息至目标线程的队列,然后监视直至处理,PostThreadMessage()进入队列后返回

其实真正处理消息的就是一个窗口过程函数,它的参数实际上就是一个简化的MSG结构,包括了:对应窗口的句柄、消息的ID、消息的参数

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

当我们创建一个窗口的时候有一个注册窗口的过程,代码如下:

ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;

wcex.cbSize = sizeof(WNDCLASSEX);

wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSP));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WINDOWSP);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}

很显然在注册时就绑定了上面的窗口过程函数,进而对各式各样的消息进行处理,

紧接着就到了从队列中接受消息的过程,消息队列中对消息的处理主要有以下三个函数:

BOOL PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg); 

BOOL GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax); 

BOOL WaitMessage();
  • PeekMessage用来判断队列中有没有消息,可通过设置wRemoveMsg来决定是否删除进行判断的消息
  • GetMessage会取出线程的消息到一个MSG结构中,如果调用了该函数且队列为空就会出现线程挂起,进入休眠状态,CPU会分配给其他线程。这里涉及到线程、进程方面的知识,以后再作详细说明
  • WaitMessage,当没有消息时使用,使线程挂起处于等待状态

当然有的消息中的内容并不能被直接识别,还需要一个翻译过程,也就是需要调用TranslateMessage、TranslateAccelerator两个函数进行处理,这里主要是键盘等外部设备用户的输入(后者是用来处理快捷键的),普通消息可以跳过。接着就是重头戏了,DispatchMessage函数,看这个名字有没有想到之前DisPatchException?它们同样是用来分发的函数,则不过之前分发的是异常,现在分发的消息罢了

  • 检查目标窗口是否存在,不存在直接将消息丢弃
  • 是否为不必须处理的事件,举个栗子,比如窗口边框没左键的功能,你还疯狂点它。如果是的话进入DefWindowProc进行下一步处理,处理很简单,再生成一个新的消息传出去,重复过程
  • 调用相应的回调函数

0x03 程序实战

该程序是username+key模式,是输入Name之后,对Name进行“算法”之后产生serial,然后比对serial。

images

该程序的重点是理解windows消息机制后找到“算法”功能(虽然可以对诸如MessageBoxA等函数下断),这才是本文的重点。

执行程序,发现该程序一直陷入GetMessageA-TranslataMessage-DispatchMessage-GetMessageA循环,写入Name与Serial之后直接弹出窗口,无法捕捉到具体算法。

在上文中我们了解到”注册时就绑定了上面的窗口过程函数,进而对各式各样的消息进行处理“,所以就可以把目光瞄准RegisterClassA函数,这个函数是接收到不同消息后决定跳转到哪里的功能函数,它的结构体的第三个成员下断,就可以找到“类switch”语句,这里我们看到是“0x00401128”。

images

找到0x00401128后就可以看到“switch”语句

images

再继续跟就可以看到“算法”功能函数,是一些加减异或,挺简单就不具体分析了,主要熟悉windows的消息机制。

images


云涯历险记 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:IDA系列教程:由软件破解谈windows消息机制
喜欢 (0)