深入理解消息循环原理
笔者先要申明,这节知识都是笔者根据自己的知识积累和观察做出的猜测。既然是猜测,那么肯定有不准确的地方。不过笔者还是对自己的推论和猜测很有信心。同时感谢我的搭档刘飞,关于这节内容,我们俩经过了比较深入的讨论,最后一致感觉就现在的想法比较的合理,比较的说得通。因此我写出来与大家分享。
Windows内核是靠IRP来驱动,而Windows窗口应用程序靠的就是消息来驱动。所谓的消息驱动其意思就是靠消息作为纽带来运作。
需要注意的是,一个窗口只能对应一个线程,用VC编写过应用程序的读者肯定对消息循环的while语句再熟悉不过,这个循环语句其实就是个死循环,这样这个窗口线程永远不会退出除非接收到WM_QUIT消息。
这里涉及到几个和消息有关的函数,分别是GetMessage,TranslateMessage,DispatchMessage
另外涉及几个和默认窗口过程有关的函数,分别是DefDlgProcA,DefWindowProc,DefMDIChildProc,DefFrameProc
现在读者肯定迫不及待的会问,这节和OD的学习有必然联系么?!!我很负责的告诉你,有!因为OD中会涉及到下消息断点。我们必须了解消息驱动原理。不然,笔者绝对相信你会很茫然看到突然中断到一个陌生的地方,而这个地方的代码你完全不晓得是干什么的。
我们开始。我们运行一个程序,最外面的大框架是一个窗口,其内部的按钮,文本框等等也都是窗口。需要注意的是,我们最熟悉不过的那个最外层窗口有消息循环。内部的按钮,文本框却没有消息循环!也就是说,最外层的窗口对应一个线程,而里面的按钮,文本框等等所有的“子窗口”都不会有线程与其对应,这些“子窗口”对应的回调函数将来都运行在“父窗口”的线程上下文中,定位更准确点的话应该说是运行在DispatchMessage函数上下文中。
笔者先来描述下关于消息的整个过程。
1,每个窗口都会对应一个线程,并且一个线程有且只能和一个窗口绑定,如果某个线程和窗口进行绑定,那么系统就再会为这个线程准备一个消息队列。如果此线程没有和窗口绑定,那么就是一个普通的线程,系统也不会多此一举为他加上消息列队
2,系统会维护一种信息,此信息记录了屏幕上所有的窗口(包括按钮,文本框等等)的区域的坐标范围。并且还会为这些窗口编上号,这些编号就是窗口句柄。需要注意的是,句柄这个东西没有准确的定义,根据情况的不同而不同,具体我下一节来介绍。拿鼠标为例,当你按下鼠标,系统就会捕获此时此刻鼠标被按下时的坐标,再查看鼠标指针坐标落在哪个窗口的区域范围内。这样一来系统就会知道哪个按钮被按下,同时系统会构建一个消息数据结构
Type MSG
hwnd As Long 这个里面的值就是此窗口的句柄(鼠标坐标落在其内)
message As Long 此时此刻应该是 WM_LBUTTONDOWN
lParam As Long
time As Long
pt As POINTAPI 坐标,就是鼠标被按下时指针的坐标
End Type
并且会把这个结构放入系统消息列队里。
3,每个绑定了窗口的线程都会通过GetMessage函数从系统消息列队中取出消息,其判断依据就是窗口句柄和坐标,如果发现系统列队中某个消息的窗口句柄对应的窗口包含在自身范围内,那么就拿过来。如果不包含在自身内,那么就无视。
4,TranslateMessage的作用是把接收过来的消息加工一下,比如说把WM_KEYDOWN和WM_KEYUP消息组合成一起称为WM_CHAR消息。
5,接下来就是DispatchMessage函数,这个函数非常的重要,首先,和上面2个函数一样,他们都存在于USER32.DLL中。此函数会分析得到的消息的结构从而得到此结构第一个参数值(窗口句柄),并且调用此按钮的默认回调函数。说要说明的是,由于按钮,文本框等控件对于程序开发人员来说都是很常用的并且这些控件的功能往往是固定,不会有特别稀奇古怪的附加功能,因此这些控件的回调函数也就可以固定下来,微软想法和我一样,同时,微软为了节约程序员的负担干脆把这些回调函数封装起来并固定存放在USER32.DLL中。需要注意的是,这些函数是非导出函数,除非你用OD跟踪,不然你还确实无法知道此回调函数在哪。这当然是后话。
6,从上面的描述你可以发现,像按钮这样的控件,其对应的回调函数已经固化在USER32.DLL中,那么DispatchMessage函数就去调用这个函数。
7,我们还知道,其实按钮这些东西都是作为资源存在于PE结构中,就某个按钮而言,你在写程序的时候也仅仅描述了以下几个信息,首先是明确表示你描述的东东的确是个按钮,其次就是按钮的坐标,其他几乎没有做任何描述,你并没有告诉系统此按钮的回调函数。系统是这样工作的:当程序运行的时候,系统要分析PE结构里所有的资源,通过资源里的描述得到具体的信息,拿按钮资源为例,当系统通过PE得到这个按钮的信息之后就会在内核中构建这个按钮的内核对象,此内核对象里的某个成员变量就会指向USER32.DLL里的某个地址,这个地址就是按钮的默认回调函数的首地址,以上这个步骤是系统自动完成的。当然,此按钮的内核对象里还会包含其他一些信息,比如坐标,资源号什么的。也就是因为按钮的内核对象里包含了回调函数的首地址,所以DispatchMessage函数才能调用!不然DispatchMessage函数压根不会知道从哪获取按钮回调函数的首地址!
8,你可以发现,当你下鼠标断点之后按F9,再尝试按下某个按钮后都会中断在USER32领空的某个代码处。你反汇编USER32后会发现这段代码并没有导出函数与其对应。所以可以判断按钮的默认回调函数的确没有被导出且确实存在于USER32库里。按钮的回调函数要做的是获得WM_LBUTTONDOWN消息并且分析之,随后生成WM_COMMAND消息(带上BN_CLICKED参数和本按钮的句柄)发送给主窗口的线程消息列队中,之后GetMessage得到这个WM_COMMAND消息并通过DispatchMessage函数调用主窗口的默认回调函数来处理这样一个消息,处理好了之后生成WM_COMMAND(带上按钮资源号)的消息再次发到主窗口的消息列队中。接下来DispatchMessage函数调用的将会是用户定义好了的窗口过程函数。这样一来按钮和主窗口的联系就建立了。
好了,这就是消息循环的细节。下节开始介绍句柄。以及程序是如何运行的。这对学习OD也非常有帮助。不然你会很迷茫。
Ollydbg学习之----句柄以及程序运行相关细节
(2010-09-16 00:35:38)
句柄以及程序运行相关细节
关于这节,也是我的总结和经验的积累。关于句柄,是个说不清的话题。因为他所代表的东西会根据某个具体的情况有所变化。
不过呢,我还是要介绍介绍相关的细节,以至于加强大家的理解和判断力。
每个进程诞生的时候系统都会生成一个内核对象EPROCESS与其对应。并且此对象结构里有个成员指针,这个指针指向一个表,这个表就是进程的句柄表。关于这个表,你可以把它看成N多个EXCEL单元格组成的队列。好了,这就是句柄表的描述。现在我来举个例子,这样你会对句柄以及句柄表有所感悟。请看:
我们拿OpenProcess和VirtualAllocEx来举例。
你可以随便查下MSDN或者说百度,他们都会告诉你OpenProcess如果成功就会返回想要得到的进程的句柄。同时你可以利用这个句柄调用VirtualAllocEx函数来达到划分地址空间的效果。
现在有个很直接的问题来了!!------什么是句柄,为什么要有句柄,句柄有什么用!!!
我来说下用户调用OpenProcess后系统是如何做的:
1,系统会调用ntoskrnl里的NtOpenProcess函数
2,NtOpenProcess函数首先会获得目标进程的内核对象EPROCESS的首地址,之后把这个首地址放到调用进程的句柄表里的某个“单元格”里。需要注意的是,放的时候不是按顺序,而是随机的。
3,目标进程的首地址放到句柄表里面之后,返回被占用的“单元格”的索引。而这个索引值就是你所得到的句柄值。
4,之后呢,你会把这个句柄值提供给VirtualAllocEx函数,此函数会调用ntoskrnl导出的内核函数NtAllocateVirtualMemory,它会根据句柄值(其实就是索引值)查找此进程句柄表,从而得到目标进程的内核对象首地址。之后从而可以进一步做自己应该做的操作。
当你再次调用OpenProcess函数之后,你会发现句柄值有所改变。这是显然的,因为得到的内核对象首地址存在到句柄表里的时候是随机的,所以返回的句柄值肯定也是不一样的。这一点很好理解。
以上是关于句柄的某一种理解方式。我刚才说了,句柄这个东东在不同的场合,理解起来是不同的。那么我再构建另一个场合。现在我们来拿窗口句柄举例。涉及到的函数有FindWindow 和 WindowFromPoint。
这2个函数都是获得某个窗口的句柄,不同的是,FindWindow是根据窗口名或者窗口的类名来获得,而后者是根据鼠标的屏幕坐标来获得。他们都会获得指定窗口的句柄。通过实验可以发现(有兴趣的读者可以自己构建程序做实验)对于一个固定的窗口,你不管调用多少次FindWindow函数或者说开启无数个程序实例来获取,其结果都是一样的。句柄值并没有任何的变化。这也就是说,你得到的目标窗口的句柄其实就是它的真正标号,FindWindow函数并没有把这个标号多此一举放入句柄表里,而是直接返回给你了。看到了吧,同样是句柄,而在这样的情况下,并没有涉及到句柄表。因此,严格意义上讲,FindWindow返回值就不应该叫句柄!!!但是呢,从程序员角度来讲,他们并不需要知道的太多,所以与其让他们知道细节反而更麻烦,还不如直接堂而皇之给一个不合理的名称。至少API函数会用就行,是不是能够正确理解句柄并不是很重要。
难道对于理解句柄的真正含义真的不重要?!!除非你喜欢遇事迷茫,一直喜欢迷茫的感觉。
我希望读者在以后的编程中一旦发现涉及到句柄,那么停下来想一想,究竟这个句柄值是什么,表示的是什么,这样一来你才会有所感悟。
由于句柄的概念不是那么的确定,因此很多书上轻描淡写,就连大名鼎鼎的看雪《加密解密》书上也只是稍微描述了下,并没有举例说明,可以肯定的讲,他们都懂,只是不愿意讲,因为概念不确定的东西写成书不是很合适。至于为什么我要拿出来分析,很简单!因为上节涉及到这个概念了,所以就干脆写下来。
现在我们研究程序的运行流程。
程序运行流程是上一节的扩充。涉及到窗口内包含有对话框的消息的处理过程。
下节开始介绍
这节到此结束。谢谢
逆向工程-------OD学习之深入理解消息循环
(2010-10-18 06:10:35)
OD学习之深入理解消息循环
其实本不打算最近一段时间学软件逆向,但是由于工作的需要,我不得不暂停驱动的学习转而开始研究逆向工程。当然啦,作为计算机界2大顶尖技术:驱动,软件逆向。我乐此不疲。
不可否认一点,逆向工程很容易涉及侵权问题,这是根本无法避免的,因为你要学习,你要积累经验,那么你就必须尽可能多的破解那些共享软件。可以说,逆向工程的魅力就在于时刻给你足够的成就感,让你内心里充满自豪和满足感。这或许是我学逆向的动力吧!当然,我本来就喜欢追求高技术,所以学习逆向工程是早晚的事情,既然工作有需要,我就毫不犹豫开始了。
好了,今天我们一起深入探讨下带有对话框的消息循环。先看下图:今天我们就来研究这个图!!
![逆向工程-------OD学习之深入理解消息循环 逆向工程-------OD学习之深入理解消息循环](https://i-blog.csdnimg.cn/blog_migrate/942259285e1e52554ecd98340a04601a.jpeg)
为了方面研究,我就选用看雪实例里的一个程序。从图上你可以看出一个主窗口,还有一个消息框。为了程序界面的简单,还请暂时把你看到的这个消息框当成对话框。这样一来你所看到的2个元素分别就是:主窗口和一个对话框。
现在我们就来模拟下你按下“OK”按钮后,系统到底做了些什么操作。
当你按下此按钮,系统就是构建一个消息结构
MSG {“按钮句柄”,“WM_LBUTTONDBLCLK”,参数1,参数2,“此时鼠标在屏幕中的坐标”}
之后把这个消息结构放到系统消息列队。之后系统会看情况把这个消息放入主窗口的线程消息队列中。
这个时候我们的主窗口消息循环中的GetMessage函数就会去自己的线程消息队列里取消息,如果没有取到,那么这个函数是不会返回的,原线程挂起同时发起线程的切换。(在此感谢刘飞同学的补充理解),如果取得消息的话,那么DispatchMessage函数就会分析这个MSG结构从而获得这个“按钮的句柄”,那么DispatchMessage就会调用这个按钮的回调函数(按钮回调函数包含在USER32.dll中)。按钮的回调函数会重新构建这个MSG,使其变成:
MSG{“按钮父亲窗口的句柄(TrackMe对话框的句柄)”,WM_COMMAND,“OK按钮的资源号”,0}
并且把重新构建好的MSG结构再次放入主窗口消息队列中。这个时候GetMessage函数继续取消息,而这个时候的消息里的内容已经有所变化,DispatchMessage函数分析新来的MSG结构获得的句柄已经是对话框窗口的句柄,这个时候DispatchMessage函数就会去调用对话框的消息回调函数,令人兴奋的是,对话框的消息回调函数是程序员自定义的。这样一来,按钮的点击就和过程函数扯上了关系。
当然啦,上面说的还是比较的肤浅,其实里面还涉及到更多的消息的转换,我就不介绍了,有兴趣的读者用OD跟下就会发现其细节。笔者认为只要理解以上这些已经足够,没有必要深入。因为你永远没有足够的经历和天赋去理解人家微软几万人的杰作的任何细节!!!!
说到这里,读者可以发现DispatchMessage会根据消息里的窗口句柄值来判断调用哪个窗口的窗口过程函数。这是本节的精髓所在。
下节我们更加深入发掘消息循环里的细节。想必会给你带来不一般的感受!
下节见~~~~~~~~
OD学习------手动脱壳有效手段
(2010-12-02 15:45:38)
手动脱壳有效手段
不可否认,壳的确是个好东西,很多时候它可以帮你拦截大多数软件分析者的跟踪。
同样,病毒的作者也有同样的感想。因此,为了避免自己辛苦写出来的病毒程序被安全工程师或者同行跟出来,他们可算是想尽了一切办法完善,加强壳的能力。
不过话又说回来,你壳再强大,再复杂,再变态,程序总会在某一时刻到达OEP,也就是真正的入口点。因此,只要你有足够的耐心和了解一些基本的注意点,那么跟到OEP还是不难的。
需要说明的,在我看来判定一个壳难不难脱的标准在于PEID能不能识别。当然,这个标准仅仅是我自己定义的,一般来说这还是符合客观事实。
PEID能够识别的壳,相对都比较的好脱。一般来说简单的壳可以通过ESP定律,F4定律轻松搞定。难的壳大都需要LoadLibrary定律来辅助。如果实在不会百度下就基本有了思路。
就杀毒软件公司的病毒样本分析人员而言,他们很少关注PEID无法探测的壳,也没有太大的把握能够把这些壳脱下来。因此,病毒如果加上这些强壳,那么至少会让安全人士郁闷一些。同时他们也不能把你的病毒细细的跟出来。这点很重要,毕竟知识产权对自己而言是至少无上的!!
很多脱壳的朋友经常会遇到这样一个问题,不停的F8或者F4之后,OD就会显示异常,之后就跳到了NTDLL领空,之后程序就退出了。关于这一点,我并没有深入研究。不过呢,据我的观察:壳作者会故意插入引起异常的代码 比如: mov eax ,0
mov [eax],888
并且在上面代码前插入SEH异常处理函数。很明显,这是故意添加的,其目的就是故意产生异常,按道理讲,这个异常的出现会导致异常处理函数的调用。如果你使用OD调试的话,此类异常会首先被OD捕获。当然,你可以忽略异常,但是问题来了,如果壳的作者在异常处理函数里添加些有用的代码(比如解压代码),那么的话你OD即使忽略了异常也再也跟不下去了。这关键的关键就是千万别忽略异常,当作为调试器的OD无法处理的异常,系统会继续交给程序来处理。正如我们所知道的,壳的作者已经定义好了异常处理函数。所以,按道理讲,你完全可以F8或者F7跟下去。但是这样做太乱了,太令人心碎了。这也就是为什么很多菜鸟只能放弃这些壳了。
因此有人想出了LoadLibrary定律。为什么会有这个定律呢?
作为壳而言,它肯定会做的事情就是要具有还原节区的功能。那么肯定就必须调用API函数,这些函数必须动态获得,因此LoadLibrary函数肯定就会被调用。对于壳而言,故意添加异常的代码大都出现的刚开始的地方,获得相关API之后才会开始还原节区的动作。还原好了之后才会跳到OEP。一旦程序运行到LoadLibrary,那么至少可以保证跳过了最开始的令人心碎的部分。
现在重点开始研究LoadLibrary定律该如何的使用。首先,我们必须知道,LoadLibrary函数可以直接被调用也会被间接调用。而在壳代码中,它肯定会被直接调用!!说白了,你下完此函数断点后按F9的时候务必关注堆栈窗口的函数返回地址!!并且在第一次调试的时候还要数清楚你按了几次F9后,堆栈窗口的返回地址不再是程序领空内部地址了(在系统领空)。为什么呢?正如我上面所说,壳的代码会直接调用LoadLibrary函数,因此返回地址肯定还是壳的领空(也就是程序领空)。如果说在某个时刻按F9,堆栈中的返回地址变成了系统领空,至少可以判定这次的LoadLibrary调用肯定和壳的正常行为无关,或许这个时候已经走过了OEP了。所以以上我所说的只有一个目的:准确定位到“万事俱备”这一时刻,接下来就开始等着开始还原节区了。
当壳获得了自己所需要的API之后,那么它就可以做还原节区的工作了。你可以按F8走。但是中途有些地方必须要注意。特别是遇到像这样的指令:
Call eax (此时eax指向一个系统领空地址)
如果说你不注意,死命的按F8的话,那么你就不知不觉进入了系统领空,像这样的情况,能走出来还算好的,走不出来就郁闷死你。这也就是为什么很多人放弃继续跟的另外一个原因。因此你遇到这些变态壳的时候,一步一步的走,如果遇到诸如上面的指令,那么按F7进去,之后再把堆栈中的返回地址记录下来并在反汇编窗口中下断。这样直接按F9就跳出来了。这样就避免了你在系统领空跳来跳去影响心情。
当然啦,这些壳还有个比较显著的特征就是CALL语句跳转的地方就在附近,因此在壳中遇到CALL语句千万记得养成先按“回车键”的习惯,如果是近跳(程序嵌套现象)经务必F7。如果你F8了,那么后果是严重的。如果是普通的跳,那么F8就行。总之一切要小心。
在壳中,Loopd指令也会经常的出现,记得必要的时候下条件断点 “ecx==1”,这样避免你按的手指发酸。
*************************
另外补充下,壳还有一个重要的特征:当它要还原节区的时候,首先必须先要修改内存属性,毕竟还原节区这个举动是要操作内存的,从而必须要修改相应内存属性为可写,可读。因此,我们可以在virtualprotect函数上下断点。上面我们已经说过,壳的作者会故意插入异常,进而执行异常处理函数内的实用代码,不排除人家会在调用LoadLibrary后面继续插入这样令人恶心的东西。因此下断virtualprotect可以更加多的越过异常。
当然,下断VirtualFree也是很好的方式,因为壳在还原节区的时候总是会申请一段内存作为交换区域。还原操作完成之后,就会用这个函数释放所申请的区域。因此在这个函数下断更加的和谐。
*******************************
读者可以用 PECompact 2.x壳做实验。这个壳很好的表现出以上几乎所有的特征。并且有些版本还会稍微加上一些反调试的功能,比如说调用IsDebugerPersent函数。像这样的情况很容易处理,要么NOP掉这个函数调用,要么就修改跳转。关于这个函数,还请读者熟知其代码细节:
IsDebugerPersent()
{
_asm
{
mov eax, dword ptr fs:[18]
mov eax, dword ptr [eax+30]
movzx eax, byte ptr [eax+2]
retn
}
}
如果读者非得细细跟带有“故意插入异常的壳”的话,你会很容易发现注册异常处理函数的代码,之后找到异常处理函数的入口点。下断吧! 之后慢慢跟下去。
其实网上有很多脱壳的教程,有图有步骤,但是本着知其然知其所以然的精神,笔者还是决定写出这篇文章让读者知道为什么要下这些断点,为什么要有那些操作。当然,笔者在这方面也是菜鸟,有些地方出现错误或者说理解有问题也是难免,还请高手们赐教。谢谢~~~
***********************
反调试:
1,rdtsc指令。
2,