汇编语言学习

更新时间:2024-02-01 15:02:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

汇编教程(1):如何学习汇编 2008-11-01 15:43

想想我自己玩电脑也是不少时间了,也经常在各大论坛灌水,得到过很多人的帮助。

但是非常遗憾的事一直都没有为别人贡献过什么,现在我有点时间所以写了几篇关于汇编的文章,我以后还会继续更新。路线是:先写点16位汇编-32位汇编-解密知识-外挂制作。希望大家关注,同时多给点意见,在下将非常感激 ,同时希望大家多多回复不要让帖子沉下去。希望多多支持 如果大家有什么问题可以给我发Email:424608@qq.com 我将尽全力帮助你解答问题!

写这篇文章(我准备把他写成一个系列),我想讲述如何从零开始学汇编语言程序设计,请千万相信汇编其实是很简单的,只要自己努力一定是可以学会的。可能由于我的技术(编程和写作)也很不到家在很多方面都写的不是很好甚至是胡说八道(但愿你不会这样认为)。如果你对此有什么意见可以给我发Email:424608@qq.com给我我将竭尽全力的回复每一封邮件同时希望大家多给我提提意见,让我把文章写的更好。。!

这篇文章里不会有太多技术方面的东西,我想在这章先和大家一起探讨如何学习汇编的问题,也许有些内容让你觉得我很啰唆。那是以为我为了照顾大部分的读者所以写的比较详细。。请大家多支持我,看了之后多给我意见。如果没有意外我会坚持每个礼拜发表一篇。今天写的是第一章,只是个引子,还没具体涉及到编程。

罗云彬说的好啊,学汇编就像千军万马过独木桥,能够坚持到最后的很少。这个不禁让我想起了高考啊。汇编这东西如果你真的学精通的话可以让计算机做很多别人不能够做的事情,因此很多人都想学习汇编。掌握这方面技术,对自身的提高确实有好处。你可以通过反汇编别人的程序跟踪别人的软件,了解别人的编程思路。通过了解别人的程序思路,使自己写出更加好的程序。而且写病毒,木马和外挂等等的一些软件~~~都必须用汇编才能够写的漂亮。研究汇编还有助于掌握一些系统底层知识,系统底层知识绝对是构造起大型软件的坚实基础。许多程序发展,都经历了这一锻炼过程的。 而大多数人可能认为汇编是一门高深的学问。很难学,我前几天在网上拿了个软件给我朋友用。 他问我用什么写的,我说用汇编。。他不信以为汇编根本就写不出强大复杂的软件,其实这都是老皇历了(现在的汇编其实我感觉和C差不多都是调用windows API来编程)。造成这种原因我觉得是以前在dos下面用汇编来写程序太麻烦,所以给人的感觉就成了很复杂,(以前在dos下汇编没有很多API可以调用)就将汇编神话了,造成初学者在心理上就承认了汇编很难学的”事实”其实不是这样现在其他编程语言可以做的比如C,用汇编做起来也不是很麻烦。再一方面由于学习资料比较匮乏学。初学者一般不知从何下手,由于没方向,花费了大量时间和精力,走了不少弯路。

这里我就给想学汇编的指下学习方向(根据我自己的学习经历)。

在学汇编前,最好先掌握一门高级语言编程,这样再学汇编应容易些,如果你学校或者在其他地方学了C语言的学起来就比较轻松了,很多书上都是用C语言程序做范例来讲解的,如果你没有学也没有关系这里我给你推荐个视频: <<尚学堂科技java系列视频教程>>,这个教程可比那些什么什么大学跟这课本念PPT的好多了, 你或者会问我为什么学汇编要去看java的视频啊,这里我只是推荐你去看下他视频的前3章,也就算基础知识,这在所有的程序设计语言里面都适用, 看完了再学习汇编坡度就不会那么陡了。

看完视频我想再向大家介绍几本书都是中国人写的(我觉得国人计算机方面的著作就算汇编比较有出席拉) 一本是王爽写的<<汇编语言>>和罗云彬写的

windows环境下32位汇编语言程序设计,这2本书在学习汇编的人心中地位都非常高现在都出了第2版了,而且网上都有电子版,google下就能找到。其他还有一些书比如: 温冬蝉,沈美明:清华大学出版社,《IBM PC 汇编语言程序设计教程》, 。【美】Kip R。Irvine 著,温玉杰等译:《Intel 汇编语言程序设计》(第四版),都是比较经典的,如果有机会可以看看!!

学习汇编要多动手,不要只看书和书上的例子,有条件就把例子弄到电脑上试一试,如果成功了,不要高兴,你的工作只完成了一半,把例子改一改,按你的意思,想想改了之后的结果,与上机对照一下,你会有收获的。 写汇编程序难免会出错,老一辈的人都还在讲解着dubug的使用,其实现在windows环境下的汇编调试器已经非常的完善了,我平时一般是用Rad ASM里面自带的调试器,他已经可以给我解决大部分由于编程粗心而引起的错误,还有一些是逻辑错误我一般 用ollydbg进行调试,这里我再给大家推荐个编辑器Rad ASM,这是一个非常棒的汇编IDE编程环境掌握他的使用可以让安心的写汇编程序而不用把心思在记忆复杂的一些编译指令上,我准备写篇Rad ASM的教程,但是现在还没有动笔。现在比较好的讲解Rad ASM可能是jhkdiy写的 Win32汇编开发环境介绍和RadAsm简明教程

学习汇编其实很累的,需花费大量的时间,而且经常会遇到很多的问题,资料又比较匮乏。这时你可能有点想退却,其实你不要着急,只要你认真学习,成功就在眼前。没有人是生来就什么都会的,如果你有问题,就大胆的去问你周围的人。而且现在网上也有很多的专门讨论汇编的站点比较好的应该是aogo的汇编小站和罗云彬的编程乐园,学汇编的秘诀就是勤奋+执着!记住并能做到这两点,你会变得很优秀的。

不管你学汇编是出于什么目的,如果你在学习汇编的过程中如果遇到了问题可以发邮件给我探讨,我非常的欢迎邮件是:424608@163.com。同时在学习汇编的时候如果突然觉得学的很痛苦,可以在看雪学院下点东西玩玩解密,培养培养下兴趣也未尝不可啊!多在机子上试试底层的东西,如bios调用,IO操作,对显卡,硬盘的控制。那样你会体会到学习汇编语言的快乐。

总之要记得想学会汇编,你需要自立自强,以及自学能力。现在开始吧??

转载请注明出自暗组技术论坛 http://www.darkst.com/bbs/

汇编语言(2):如何学习汇编 2008-11-01 15:44

准备提笔写的时候好像不知道怎么下笔啊,毕竟以前我都没有写过技术方面的文章,怕自己技术不够让高手笑话,又怕表达不清楚自己的意思.但是心一横死就死吧!在上一篇 如何学习汇编中没有给大家扯太多技术话题,但是我觉得那也是非常重要的,学习什么事有了信心就成功了一半,所以还希望大家继续坚定要把汇编学好的思想,要相信自己汇编是很简单的,我们通过自己的努力一定可以把汇编学好

那么让我们开始今天的话题?..

今天主要扯点计算机的工作方式,因为汇编是面向机器的语言,所以非常有必要了解下计算机的结构,CPU的工作方式.这里我假设你只有一点点最基本的电脑基础啊,最好自己去弄懂那个什么进制转换的,我觉得特麻烦,不过却也比较简单..我这里就不和大家扯了自己了解下~~那么既然假设你没有什么电脑知识就会显得有些啰唆,高手别见笑啊!

说到汇编语言啊,就扯远拉,远到哪里呢, 嗯很远, 汇编语言和机器语言是很亲密的,所以要聊下汇编语言那就不得不又扯下机器语言了,^_^.真远啊!! 机器语言

什么叫机器语言呢,机器语言就是一些机器指令的集合拉,也就是一些二进制的数字什么010010的,这就是机器指令,计算机呢可以把这些010010什么的转变为高低电平,使计算机的电子器件收到驱动,进行运算..嗯,搞的好像很复杂似的,其实简单理解就是计算机也有一门语言(机器语言)就像我们的汉语一样,我们汉语的构成呢是由字来构成的,机器语言(也就是电脑自己的语言,就是一些01001什么的来构成的.当初我理解的时候就想啊,机器语言不是人发明的么,为什么不教他学汉语啊要教他学什么01001的那么麻烦^_^.呵呵其实现在很多人都在做着这样的努力呢, 诶~~又扯远了.

给大家扯了机器语言的那就再给大家扯一下计算机吧,你有跟朋友去过电脑城装过电脑吗?或者见过别人把电脑机箱打开后里面的情况吗? 对里面有一个大风扇,大风扇下面知道是什么吗? 那是CPU就是电脑的心脏,那家伙就像我们脑袋一样是指挥部.其实我们见到的那CPU叫微处理器,是它控制着电脑的显卡啊,声卡什么的,当然我们不能够直接去控制这些东西那些玩意不听我们的,但是他们的指挥官(CPU)听我们,我们编程的任务就是指挥CPU(嗯,你可以把这想象成一场战争)去控制显卡显示图像,声卡发声.是不是很兴奋,可是我要跟你说的是他们的指挥官(CPU)是个莽夫特别讨厌学习国家的语言(这个和我倒是有些类似

啊).所以我们想要和CPU沟通就要学习他的语言,可是他的语言实在是太难学了(有点向英语那么难学^_^)所以我不准备去学习他的语言,同时我也不建议你去学它的语言,那你要问我怎么办拉,嘿嘿很好办,你看过胡锦涛和布什谈话的时候是怎么谈的吗? 对拉有翻译,毛主席说人人都是平等的,胡主席可以请翻译,我们也可以~~~~我们的翻译的就是 汇编, 汇编是个好同志啊,他帮我们把指令翻译成很长很长的0101001,从此我们就离开了机器语言的怀抱,而用汇编来控制CPU拉,这是什么,这是世界的进步啊,是不是又感觉这世界又美好了一些!! 汇编语言

那么,我们现在已经理解了汇编语言和机器语言的相同和区别了,要说相同的话他们都好像是4个汉字,区别就是前2个字不同,倒下一片~~~~~~“, 其实汇编和机器语言在我理解是没有什么区别的,汇编只是把机器语言的010010对应的翻译了一下,就像我学英语一样,老是喜欢做硬式翻译,所以此时此刻我不得不再打击你一下,因为汇编只是硬式翻译了下机器语言,所以造成了汇编的指令过多,和过于复杂了.当然后来为了解决这个问题又出了C语言对吧,你不会想当叛徒去投靠C吧,晕,张三丰说过,千万不能够当叛徒,这不,现在的32位汇编就有宏汇编,搞的和C都差不多了,所以你也别想着要跑C那里去,现在我们只要学习下DOS下的汇编理解了那些指令的用处,到时候我们在windows下面写程序的时候都有很多宏可以调用了.. 汇编语言的组成

那么我们现在把机器语言扯完了,(其实我还想再扯一会的).但是怕你不满意!!下面就是汇编闪亮登场了,你也许会问我汇编长什么样子呢?? 这不好说啊,但是我可以肯定的是他绝对没有我帅^_^?其实汇编不能够说长什么样子,应该说汇编由什么东西构成呢, 那么我告诉你汇编是由 一些指令 比如你看着头晕的mov啊什么的, 还有就是伪指令了,伪指令就是些段标记啊什么,现在不知道没有关系,到时候我再跟你扯.. 存储器(内存)

现在我假设你已经把汇编指令学完了,那么你肯定是命令计算机执行几条指令对吧,理论上说的过去,但是现在还有个小问题就是..你在什么地方给计算机发布命令呢? 计算机可不是很随便的人啊, 计算机很死板死板到什么程度呢,它只能够在内存(存储器)中接受我们的指令.其他地方?sorry不行.所以我们必须把我们的指令放到内存里面让CPU来读取和执行,所以知道内存的作用了吧,很重要吧,那么我们当然要了解下CPU是如何来读取我执行我们的指令的对吧..所以我们的学习环节里面又多了个,了解CPU是如何在内存中读取指令和写入指令.. 那么到底CPU是如何去读取指令呢,是这样的,CPU把内存划分成一个一个的单元,单元里边的编号是从0开始的,比如有128个存储单元,那么第一个就是0 最后一个就是127,你现在肯定想问拉,那一个存储单元可以存多少信息呢?? 计算机把一个2进制位称为一个比特(bit)8个就是一个字节了,(Byte)实际上现在的电脑啊它的内存是大的不得了的,我们只有了解这个概念就成,比如我现在写文

章这台的电脑就有512M内存,那么换算下来是多少呢?

1KB = 1024 B(Byte) 1MB = 1024 KB 1GB = 1024 MB 1TB = 1024 GB

你可能要问了,我这是不是在讲硬盘啊,硬盘上面才用多少个G啊这样标示啊,其实内存和硬盘是一样的,他们的计量单位都是按照上面那些公式来的. 存储单元

刚才给大家扯到,内存被分为多个存储单元,存储单元是从0开始编号.你可能有疑问为什么要编号呢,这里就可以给你回答了,这就向一条街啊,你找一户人家肯定不好找对吧, 但是给他们的房子都弄个编号那就好找了..其实CPU读数据就向找人一样啊,根据门牌号(存储单元编号)这样找就非常快拉. 地址总线

既然说到了编号啊,就给大家聊点编号的问题吧,CPU本是有限啊,只能够指定特定多的存储单元,为什么啊,这个就是由于CPU的地址总线的限制了,比如啊我们的身份证是18位的,当我们国家的人口啊超过18为数字能够容纳的范围的话是无法标示的对吧(不过估计目前是无法达到这个数字的,这都是计划生育搞的好啊),那么现在是CPU有多少地址总线的话就只能够对特定多的内存进行寻址,如果再多出来的内存是没有用的,我们假设CPU有10根地址总线那么我们来看下他的寻址情况,在电子计算机中,一根导线可以传送的稳定状态只有2种,0和1 那么10根地址总线可以标示的范围为, 2的10次方个,最小就是0,那么最大就是1023了.所以可以得出结论,一个CPU有N跟地址线,则可以寻址的范围为2的N次方个内存单元. 数据总线

既然聊了关于存储器(内存)的那么多,不妨再聊点比较有趣的事情,聊什么呢,聊点关于CPU读取指令的问题,首先我想向大家提个问题,比如我现在在内存中有1000条指令啊, CPU来读取这些指令的时候可以一次全部读完吗? 这个问题啊,据我所知啊我们现在用的CPU是没有办法一次全部读完的,那么它怎么办呢,你也想到了,就是分多次来读取对吧.那么问题又来了CPU一次可以读取多少条指令呢? 这要看CPU的数据总线的宽度了,8086的CPU数据总线宽度为16位,是16位2进制的数据比如现在传送 89D9H 8086CPU就可以一次传送完,如果是89D898那么这个数字16位就标示不完了,所以要分两次来传送.

你看到这里我不得不佩服你的勇气,你是一个精神可嘉的人,虽然我写着很累,但是相信你看着也不轻松啊,诶..主要是因为我文采不好写的很烂,让人不大容易明白,其实这些东西都是概念性的东西,我这人最讨厌概念性的东西了,所以我学汇编的时候也是囫囵吞枣就过去了,所以现在给大家讲起来也底气不足..不过没有关系这些概念性的东西没有弄懂也没有关系,了解下就成了,等你到时候把汇

编学个入门后再倒回来看就会获益匪浅的.

回忆下我们吹了什么东西, 机器语言, 汇编语言明白了吗? 存储器(内存)的作用明白了吗?存储单元和地址总线还有数据总线这些明白了吗? 如果明白了就过关了,如果没有诶~~~~不是你有问题就是我有问题了..

这段终于给大家吹完了,我也松了一口气,以后的扯淡应该会更加轻松些.所以别紧张.下一篇可能要过几天了,这几天比较忙啊!下一篇就给大家真刀实枪的干了,我们就要接触汇编指令了,期待吧! 请多多支持我的blog,你们的支持是我最大的动力..有动力马才跑的快啊!

汇编教程(3):寄存器CPU工作原理① 2008-11-01 15:54

你真的弄懂了汇编语言基础?? 真的吗? 如果是那么就继续今天的话题啊!!在对CPU读取指令方面有了一个感性的了解后,我们就应该学学寄存器了,寄存器可以说是汇编代码里面必然存在的东西,不然这汇编程序就没法写了,那寄存器是什么呢?CPU到底是怎么样操作寄存器的呢???

现在的孩子真幸福啊,今天我一个18岁的朋友来象我请教如何配置电脑的问题,呵呵,想象一下啊我18岁都在做些什么呢? 不过非常不幸的是,我那朋友想买电脑来打完美世界.这不得不让我吃惊啊,现在的孩子虽然得到的东西多了,也不知道他们是否知道珍惜啊!!

好了不跟大家扯这些家常了,我本来文采就不怎么好,写这个教程权当自娱自乐啊,希望高手要求不要放太高,但是有错误是一定要提的! 上次写的那篇 汇编基础 收到了不少朋友的反馈,他们说我取的名字不好,不应该叫汇编精通啊(前两篇这个教程叫汇编精通),和我这样的写作风格不相配,想想也是哦,所以改成了现在这个名字.还有不少朋友给我提了很宝贵的意见,其中有位朋友说我写的跑题了..其实我写这个教程不打算教会你汇编,我也没有那个本事,我只是想大家对汇编有个感性的认识.. 或者是你在阅读其他书籍的时候有什么概念上的东西觉得干涩的,我给大家做个感性的介绍而已,我的想法是: 如果你想学习汇编,你手里起码得有几本书吧,比如我在如何学习汇编 那篇文章中给大家介绍那本 王爽写的 汇编语言 就非常不错,我就是靠这本书带入门的,所以希望阁下学习汇编还是以书本为主啊, 我本来技术就有限.甚至连一些概念上的东西弄错了都不知道, 惭愧惭愧啊..

上节课给大家介绍了机器语言和汇编语言,并且对CPU读取指令有了一个感性的认识,如果你没有读懂的话请给我发送邮件 或者参看王爽写的 汇编语言..其实那篇文章说的东西你不懂也没有关系,我觉得和写汇编程序关系不大,特别是写

win32汇编程序.. 既然如此就让我们了解今天的话题 CPU的工作原理.. CPU的工作原理

其实CPU从不亲自去作显卡显示图像,声卡发出声音这些事情,而是CPU在自己的逻辑存储器(这是假想中的,这个操作对我们是透明的)地址空间里通过控制总线进行操作,那些外部器件自己在逻辑存储器里面找块地方让CPU分个地址..这个说起来好像比较拗口啊,没有关系如果你不懂也关系不大,如果觉得我讲的不太明白可以参考 王爽汇编

对于一个汇编程序员来说不必要深入的了解这些东西,我们在写程序的时候主要是和寄存器打交道我们通过操作寄存器来达到控制CPU的目的.所以不了解以上讲的问题不是很大的.. 寄存器:

既然我们用汇编写程序主要打交道的是寄存器,那么我们有必要深入的了解下寄存器了,寄存器是什么呢? 它其实和内存是一样的东西,只是更加快而已,所以别被他吓倒,那么有多少个寄存器呢? 我可以非常难过的告诉你,寄存器的个数非常多,诶当初我就是看到这个才放弃学习汇编的,不过后来有幸看到了王爽的汇编语言后,才发觉原来寄存器也不过如此啊,我知道你现在想深入的了解寄存器,但是请别急慢慢来,,我们先了解4个寄存器 AX BX CX DX..这4个我们用的最多了,我知道你现在又非常的想了解寄存器到底有什么用,但是请听我说,你还需要具备一点东西,来伙计,我再给你介绍2条常见的汇编指令,非常容易理解: mov指令,add指令..它们的用法是这样的:

mov ax,19 将19送入AX AX = 19 add ax,18 将寄存器AX的值+18 AX = AX + 18

简单吧,学过高级语言的优势就出来咯,如果你还不懂高级语言赶紧去看下 尚学堂 那个视频啊! 那来考虑下我们在高级语言里面这样一段代码 int a = 10; int b = 20; int c; c = a + b;

我们用汇编该怎么来表示呢?? 我想应该是这样的 mov ax, 10

mov bx, 20 add cx, ax add cx, bx

明白了吧,寄存器是什么玩意,现在不嫌弃寄存器的个数多了吧,也不觉得寄存器多有什么问题了吧, 毛主席说:代码就能够说明一切问题啊,这应该是有道理的..那么到底有多少寄存器呢,总共只有4个AX, BX CX DX..但是对我们的编程来说一点问题也没有有了4个就够了~~~..

在8086以前的CPU都是8位的..8086是16位的机器上面的那些寄存器都是16位的(明白什么叫16位了吧),那以前8086以前的CPU呢怎么办呢?intel向了个折中的办法,就是把这些16位的寄存器再进行分家,就分成了高8位和低8位比如

AX 就分为 AH AL BX 就分为 BH BL CX 就分为 CH CL DX 就分为 DH DL

这里H 和L的意思分别表示High和Low好理解了吧,其实它们的用法也是一样的,我们就挑AX来讲讲, 比如现在有一个数字43E4储存在AX中那么AH的值为43,AL为E4为什么呢?intel的CPU是把数据按照高位到低位排列的所以AH(高8位)就存储了43,AL(低8位)就存储了E4,就那么简单,由上我们也可以看出CPU可以处理的数据只有2种,一种是字(word占16位)一种是字节(byte 8位),一个字存放在16位寄存器中,这个字的高位字节和低位字节自然就存储在这个寄存器的高8位和低8位了..比如 AX = 3454H

那么自然的 AH = 34H AL = 54H

同时我们又可以把AH和AL当作单独的两个寄存器来使用它们都互不影响的!如: AH = 42H

那么 AL还是为54H 但是AX却改变了 AX = 4254H,那么寄存器的内容就这些了,只是有些细节要注意下,请看代码: mov ax, 8F35H

add ax, 4E34H add ax, 9F45H

那么现在AX的值为多少呢? 你可能会认为是17CAF,但是我告诉你AX=7CAF,为什么呢如果一个值超过了它所能表示的范围,那么它将把进位丢弃(不是真的丢弃,但是我们现在可以这样认为)同样的事情发生在8位寄存器上,请看: mov al, 8FH add al, 4EH add al, 45H

那么AL的值是22,它同样丢失别以为他会进位到AH或者其他地方,这里我们如果进行的8位运算CPU就只认识8位寄存器,其他的不管,所以以下这些指令都是错误的:

mov ax, bl ‘把16位和8一起用是错的 mov bh, ax ‘把8位和16为一起用也是错的

mov ax, 20000000 ‘把一个大于寄存器的值赋给寄存器也是错的 add al, 10000 ‘将一个大于8位的数据加到8位寄存器也是错的 嗯,终于完了,这是一场攻坚战啊,学汇编和学C不同如果上来就Hello World的话估计大部分人都的晕,所以我们还是要先打下基础啊,虽然现在还是纸上谈兵,但是我们毕竟已经接触到了汇编指令了,再过两结课我们就可以上机写写指令了,但是现在还不行,先好好体会下..接下来我们还要讲下CPU是怎么给出物理地址..好好体会,如果觉得我讲的哪里不对和不明白一定要告诉我!! 我建议你学习的时候结合下书本,效果肯定要好很多的.

汇编教程4:寄存器CPU工作原理② 2008-11-01 15:55

最近在网上看了长篇小说<<疯狂的程序员>>。。嗯不错不错,看来这做程序员的人还大多有些相似的,不过每个人却还是有些不同的,就象我。。诶到现在还是一无所有,人财两空啊~~~~~~命苦不能怪政府。。

上一篇 寄存器CPU工作原理1,给大家介绍了几个寄存器,那几个寄存器因为

是最常用的所以也叫通用寄存器,相信大家对这寄存器还是有了一个感性的认识了,但是CPU不能够只访问寄存器的,它要做更多的事情,比如读取内存。这应该说是CPU的基本功能之一吧,所以我们不了解下CPU访问内存的方法确实有点那个啊。。。 既然这样我们还是有必要了解下的。。。。。

CPU要访问内存它必须给出内存单元的地址,同时这些单元都是一维线性的。每一个内存单元都有一个唯一的地址。。这在前面的章节有所提及,那么CPU是怎么样给出内存单元的地址呢??想要了解这个问题,我们还得看看CPU的结构。。 我们通常说的8086CPU是16位的,那么这个16位代表了什么概念呢? 这个嘛。。 概括起来的讲啊主要是这几点。。

寄存器一次最多只能处理16位的数据。 寄存器的最大宽度为16位

寄存器和运算器之间的通路为16位

但是问题就来了,8086有20位地址总线(20位地址总线的寻址能力就有1M了),而CPU内部只能够处理16位的数据那改怎么办呢??这个问题嘛,当然不可以浪费了地址总线对吧。。~~如果是地址总线也改成16位的那么我们的CPU寻址能力就只有64K了,那可是天壤之别啊。。当然的想个办法对不。。别人说intel里面的人都是天才,从这方面说的话还是有些道理的。。他们想到了在CPU内部做一个加法器来进行合成一个20位的物理地址。。那么合成过程是怎么样的呢??嗯,,,好的请听我细细道来。

首先CPU中的相关部件(这个部件我们马上要提到)提供2个16位的地址,然后送入一个叫地址加法器的部件,然后地址加法器再把两个地址合成为一个20位物理地址。。那么现在问题又来了对吧。。。他是怎么样合成物理地址的呢??没有关系这里有个公式。。。套公式总是比想问题简单些的。。。额。合成地址的规则是这样的:

物理地址 = 段地址 * 16 + 偏移地址。。

毛主席说过,实践才是硬道理啊,给大家套个公式就明白了,比如现在CPU要访问内存地址单元124C8H的内存单元,那么我们就可以让段地址为1240H,然后让偏移地址为00C8H,套用公式就比较简单了: 124C8H = 1240H * 16 + 00C8H

注意拉,这里别弄错了,这里我们讨论的数据都是16进制的,所以*16只是把小数点移动了一位而已,嗯。。是不是已经感觉到了16进制的好处了。。当你在感叹16进制是多么好之余有没有想过。。我刚才写的那公式里面讲了什么。段。。段地址。。什么的!这个段地址又是什么概念呢?? 千万别以为是一段一段的地址啊!,其实内存里面是绝对没有分段的,分段只是来自CPU自己。其实分段以后是有好处的,这样我们就可以把一些连续的内存当作一个段来使用,套用上面的公式,段地址不变。。只是改动偏移地址。。哈哈这样就方便多了。我们知道偏移地址也是16位的,那么一个段最长呢也只能有64KB而已,明白了吗? 以为偏移地址只能够表示64KB的内存而已。。。

前面我们讲到了”CPU的相关部件提供两个地址”,这么一句话。。那到底是什

么东西在提供段地址呢??又是什么东西提供偏移地址呢??,这又不得不让我想到了寄存器,回忆我们前面学习的(应该还不至于忘记吧)4个通用寄存器,那么是不是CPU内部只有那么多寄存器呢?不是的。。呐。我这里就再给你说一个寄存器。这个寄存器就是专门用来放段地址的,它就是CS寄存器,不过好像叫寄存器就俗了些,不可以体现出它和其他那些寄存器的区别来,因为那些寄存器都是放一般性数据,,这CS寄存器可就不同咯。。所以又给它改个名字,就叫段寄存器吧。。这样理解应该比较好理解吧!!

现在还有个小问题,我们有了一个叫CS的段寄存器,,那还得有一个放偏移地址的寄存器啊,这次倒霉的就是CPU中一个叫IP的寄存器(嗯,别和IP地址的IP弄混了)。。啊,原来是这样啊。现在是万事俱备只欠东风啊。。就让我们来套用这个公式吧。。

物理地址 = CS * 16 + IP 就这样,给出了物理地址,事实上当你为CS为什么不叫EX或者其他的名字的时候,我得告诉你一个天大的秘密:

在8086的CPU上,任意时刻,设CS中的内容为M,IP的内容为N,那么8086将从内存:

M * 16 + N 单元开始执行。。

你可能不以为然呐,这有什么了不起啊,我跟你说这就错了,有了这玩意可以玩的事情可就多了。。你想啊我们想让CPU执行一条指令我们要怎么办呢? 想到了吧,嘿嘿。。让我值得高兴的是,我比你先想到~~~~我们只要设 M * 16 + N 的值为我们想要的内存单元地址那不就得了吗? 对的,确实是这样。。 在任意时刻 CPU将CS : IP指向的内容当作指令执行 为了让你有个更感性的认识我非常有必要举个例子,这是毛主席说的,实践才是硬道理啊,比如我在 内存20000 -20003处有条指令,那么我们想让这条指令执行一下该这么办呢?我想应该是这样的 1。 让CS : IP的值指向 20000处 2。 读取并执行指令,同时IP值要加3

我说的第一点可能比较好理解了,可为什么要加3呢,其实是这样的,你想想啊。。。CPU从20000处开始读取指令,读取到指令后就会返回,然后呢执行,CPU在执行完了这条指令后当然知道这条指令是占几个字节,这里是3个字节。。所以CPU就会把IP的值加3。。。嗯,明白了吧,原来是这样啊。

知识这东西啊,确实是好东西。当然你得先把它学会对吧。。现在让我们回想一下,那个电脑为什么在一按电源就会呼啦啦自动执行指令。。那么我们由此可以判断出,一定是有什么东西在修改着CS : IP的值对吧。。那么事实上是这样的。。 8086在加点启动后(或者重启了电脑) CS : IP的值被分别设置为CS=F000H, IP=FFFFH也就是说CPU在启动的时候从FFFF0H单元开始执行第一条指令,有趣吧。。

所以我们可以得出结论:

想要让指令执行必须让CS:IP指向它,反过来,一条指令如果被执行了,那么CS:IP一定指向过它。。

是不是已经按耐不住了,忍不住想写几条指令给CPU执行了??事实上我是一个不

喜欢调人胃口的人,但是此时此刻我不得不提醒你,你还记得mov, add。指令吗?不会那么快就把这两条指令忘了吧,如果忘记了。那么你应该再回头看看 寄存器(CPU工作原理1), 如果你还记得那么就比较简单了,例如我们还是在内存20000-20003处有条指令,那么我们可以这样写代码: mov cs, 2000H mov ip, 0H

如果你这样写的话,是要出错的,因为intel公司不给你这样干,这样写的指令是错误的。。你可能想问下为什么不行,那么我可以给你电话,你打电话去intel问下就清楚了,那么既然intel不给我们这样干,他总的提供让我们怎么修改CS和IP的值的方法吧,那么我可以明确的告诉你,方法是有的,就是用另外一条指令,它就是 jmp,使用方法:

同时修改CS 和IP的值: jmp 段地址 : 偏移地址 jmp 1000:2

只修改IP的值: jmp 某一合法寄存器

jmp ax ‘这条指令执行的效果和 mov ip, 0H的效果差不多

如果你看到了这里,并且前面的教程你都看了,并且明白了,那么我不得不佩服你的勇气,与毅力,好好努力,相信自己一定可以把汇编学好的。相信你看到这里也开始心里甜了吧,很多奥秘开始向你解开,我当初就是学到这里的时候想,我一定要把汇编学下去,并且要学好。。相信你也是一样,下一篇我就要给大家将debug的使用了,虽然那东西简陋但是还是有必要了解下的。。因为它简单,学起来方便。。OK今天就到这里吧,晚上2点了!

汇编教程5:debug的使用 2008-11-01 15:56 嗯,这次距离上次文章发布快两个礼拜了,今天我终于痛下决心了。要再写一篇。写到一半,好友 “冰横”,来向我介绍黑莓手机,,黑莓??什么是黑莓手机。诶!看来啊,落后拉。。后来查下资料才发现,这东西确实好啊,又便宜,功能又多。。有时间挑一个。然后在写这篇文章的时候,要找一个屏幕截图的软件,我用的是SangIt这个软件我可是用了好几年了6.0就开始用了,现在都9.0了。非常不错。在WP里面上传的时候弄了好几次都没有成功,不过最后还是成功拉!废话少说,开始今天的话题:debug的使用

上次我们讲了CPU给出物理地址的方法、和介绍了2个段寄存器 CS 和IP。虽然我已经写了好几篇了,但是还没有涉及到任何的编程知识,这在学习高级语言里面是不敢想象的,学高级语言就是好啊,比如C。哈哈上来就写 “hello world”,汇编可不行,,写完这篇,我们还不能写出 “Hello World”,还要

下几篇才可以,不过别泄气。通用寄存器那章都挺过来了,这两篇不难。 本来我是想直接和大家讲 “Rad ASM”想想还是作罢,还是讲讲 “debug”吧,让大家了解下计算机内部的原理。实际上你以后在编程工作中根本就用不到“debug”,至少我是很少用到,我都是用 “Rad Asm”里面集成的调试器,直观,方便。所以这章也不用死记,只要了解原理就成。

“debug”是什么呢?debug是一个软件,是一个调试其他软件的软件,既然是软件,当然要启动拉,在windows 2000/xp下启动可以这样,[开始]-[运行]-[cmd]。打开命令行窗口,然后输入命令 [debug],这样就启动了程序 debug是古老的命令行方式运作,命令繁多,一下介绍怕消化不完,那么我们只学习几个常用的。 -R

查看,改变寄存器的值。 -D

查看内存中的内容 -E

改写内存中的内容 -U

将内存中的机器指令翻译为汇编指令 -T

执行一条汇编指令 -A

以汇编的指令的格式在内存中写入一条汇编指令

这就是我们最常用的debug指令了。这样罗列出来给大家看,非常的不直观。。我们就来做几个实例虽然我个人非常不喜欢用图片来讲解计算机的原理性的东西,但是不可否认,这样更容易让人理解:

-R: 查看和修改寄存器的值:我们已经学过6个寄存器了,4个通用的,2个段寄存器。。

上面是在命令行里面显示的信息,,非常简单、你在自己电脑上试试就可以了,

告诉你一个小密码,这个命令是系统自带的,所以别问我哪里下载(这个软件在MS-DOS的时候就是系统自带了,所以现在的系统里面都有这个软件。如果你看不明白请在自己的电脑中试验下,实在不明白请看 王爽《汇编语言》,首先最上面是输入了-R命令,然后显示出了很多寄存器的值,有几个我们是认识的,有几个我们还不认识,不过没有关系,我们会在以后的时间介绍,现在就先不管了,反正记得这是显示寄存器的值就是拉,左下角这里显示的 CA12:0100你仔细看看就明白了,刚好对应CS:IP的值对吧。旁边的ADD DH[ DI +48 ] 这就是当前CS:IP指向地址的指令。同样的右下角还有个DS: 0048 = 00这个也不用深究,现在不用了解。。

命令就是这样,很简单,自己试验试验就直观了,如果要改变一个寄存器的值可以这样:

输入命令-R接着要修改的寄存器,然后回车,就输入想要修改寄存器的值就成,然后你要记得举一反三,修改其他寄存器的值方法也是一样的,包括CS:IP寄存器。所以说是非常简单的。。

-R命令:查看内存中的内容:我们可以用 d 段地址:偏移地址 的方式查看,使用方法如下:

输入命令后,默认是显示从你输入的物理地址开始128个内存单元的内容,右边全是点?的这里是该内存单元内容所对应的ASCII码。如果你嫌弃它给你显示的内存单元少了或者多了, 比如现在我们要显示1000:0-1000:23内存单元的内容该怎么做呢? 我们可以试试这样: d:1000:0 23 。这样就把1000:0-1000:23内存单元的内容全部显示出来了,一个不多,一个不少。这里就不截图了,自己试试。

-E命令:改写内存单元的内容:这个比较简单,截图一看你就明白了: 非常简单吧,我们先用d命令查看了1000:0处的内容,然后用e命令修改内存单元的内容,总共修改了11个从43开始到32结束。当然你也可以写入ASCII码值,道理和写入数字是一样的,不过记得字符串要加冒号”\是英文的冒号。 -U命令:查看内存中该地址对应的汇编指令:

可以看出来,这条命令是非常简单的,这条命令和-D很像,-D是查看内存中的ASCII码,-U是查看对应的汇编指令。非常简单。所以看到这里你就应该明白,在内存中执行指令和数据是没有区别的,关键是CPU如何解释了,如果你让CS:IP指向它,那么这个地方就是执行指令,如果你让~~~,呵呵那个还没有讲呢,下次再讲。

-A命令:以汇编的形式在内存中写入指令:使用方法也是非常简单: 我们首先用-A在2000:0处写入了3条指令,然后用-U查看了2000:0-2000:10的汇编指令,结果当然是显示我们刚才写入的指令了,所以说非常简单。看也看

了,写也写了,追究是为了执行,所以我们来看看如何执行汇编指令。 -T命令:执行一条指令,如果没有给出CS:IP,那么就执行默认的CS:IP处的指令:

T命令也是非常简单的,上图我们是先用-R命令查看了寄存器的值,然后用-R修改了CS和IP的值,然后用-A在2000:0处写入了一条指令,最后用-T执行了一下,注意看AX的值,是不是加1了,非常简单吧。。

到这里debug最简单的使用方法就讲完了,这篇我写的特别累,主要是截图,我喜欢安静的写,这样可以让自己的思绪有条理,所以我喜欢汇编,不喜欢用VB,或者delphi这样的可视化工作界面,用汇编你想做什么就做什么,只要你能够想到,限制你的只有你的知识和你的想象空间。debug就已经讲完了,是不是意见有了一些成绩了?你可能不以为然,说这debug能够做什么呢??其实可以做的事情多了,如果你是在没有任何开发工具的情况下,又需要写个小程序做点事情(好像只有黑客这么干),那么debug是非常不错的选择,在网吧或者其他别人的电脑里面想做点破坏你就可以写点汇编指令。非常不错、什么开发工具都不用。当然现在你还没有那个能力,等你再深入的学习下你就可以做到了。 这一篇又写完了,下面我们主要讲下栈,等讲完了栈就可以开始用记事本写自己的“hello World”了,在如何学习汇编 就已经讲过,学习汇编是很吃苦的事情,但是,如果你越过了这道槛,你就会获得很大的收获。有听过 “浴火重生”的故事吗?传说中,凤凰是人世间幸福的使者,每五百年,它就要背负着积累于人世间的所有不快和仇恨恩怨,投身于熊熊烈火中自焚,以生命和美丽的终结换取人世的祥和和幸福。同样在肉体经受了巨大的痛苦和轮回后它们才能得以更美好的躯体得以重生.

脱壳的艺术!(1) 2008-11-01 20:27 概述:脱壳是门艺术——脱壳既是一种心理挑战,同时也是逆向领域最为激动人心的智力游戏之一。为了甄别或解决非常难的反逆向技巧,逆向分析人员有时不得不了解操作系统的一些底层知识,聪明和耐心也是成功脱壳的关键。这个挑战既牵涉到壳的创建者,也牵涉到那些决心躲过这些保护的脱壳者。 本文主要目的是介绍壳常用的反逆向技术,同时也探讨了可以用来躲过或禁用这些保护的技术及公开可用的工具。这些信息将使研究人员特别是恶意代码分析人员在分析加壳的恶意代码时能识别出这些技术,当这些反逆向技术阻碍其成功分析时能决定下一步的动作。第二个目的,这里介绍的信息也会被那些计划在软件中添加一些保护措施用来减缓逆向分析人员分析其受保护代码的速度的研究人员用到。当然没有什么能使一个熟练的、消息灵通的、坚定的逆向分析人员止步的。

关键词:逆向工程、壳、保护、反调试、反逆向

1简

在逆向工程领域,壳是最有趣的谜题之一。在解谜的过程中,逆向分析人员会获得许多关于系统底层、逆向技巧等知识。

壳(这个术语在本文中既指压缩壳也包括加密壳)是用来防止程序被分析的。它们被商业软件合法地用于防止信息披露、篡改及盗版。可惜恶意软件也基于同样的理由在使用壳,只不过动机不良。

由于大量恶意软件存在加壳现象,研究人员和恶意代码分析人员为了分析代码,开始学习脱壳的技巧。但是随着时间的推移,为防止逆向分析人员分析受保护的程序并成功脱壳,新的反逆向技术也被不断地添加到壳中。并且战斗还在继续,新的反逆向技术被开发的同时逆向分析人员也在针锋相对地发掘技巧、研究技术并开发工具来对付它们。

本文主要关注于介绍壳所使用的反逆向技术,同时也探讨了躲过/禁用这些保护措施的工具及技术。可能有些壳通过抓取进程映像(dump)能够轻易被搞定,这时处理反逆向技术似乎没有必要,但是有些情况下加密壳的代码需要加以跟踪和分析,例如:

需要躲过部分加密壳代码以便抓取进程映像、让输入表重建工具正确地工作。 深入分析加密壳代码以便在一个反病毒产品中整合进脱壳支持。

此外,当反逆向技术被恶意程序直接应用,以防止跟踪并分析其恶意行为时,熟悉反逆向技术也是很有价值的。

本文绝不是一个完整的反逆向技术的清单,因为它只涵盖了壳中常用的、有趣的一些技术。建议读者参阅最后一节的链接和图书资料,以了解更多其他逆向及反逆向的技术。

笔者希望您觉得这些材料有用,并能应用其中的技术。脱壳快乐!

2 调试器检测技

术 本节列出了壳用来确定进程是否被调试或者系统内是否有调试器正在运行的技术。这些调试器检测技术既有非常简单(明显)的检查,也有涉及到native APIs和内核对象的。

2.1 PEB.BeingDebugged Flag : IsDebuggerPresent()

最基本的调试器检测技术就是检测进程环境块(PEB)1中的BeingDebugged标志。kernel32!IsDebuggerPresent() API检查这个标志以确定进程是否正在被用户模式的调试器调试。

下面显示了IsDebuggerPresent() API的实现代码。首先访问线程环境块(TEB)2得到PEB的地址,然后检查PEB偏移0x02位置的BeingDebugged标志。 mov eax, large fs: 18h mov eax, [eax+30h]

movzx eax, byte ptr [eax+2] retn

除了直接调用IsDebuggerPresent(),有些壳会手工检查PEB中的BeingDebugged标志以防逆向分析人员在这个API上设置断点或打补丁。 示例

下面是调用IsDebuggerPresent() API和使用PEB.BeingDebugged标志确定调试器是否存在的示例代码。

;call kernel32!IsDebuggerPresent() call [IsDebuggerPresent] test eax,eax

jnz .debugger_found

;check PEB.BeingDebugged directly

Mov eax,dword [fs:0x30] ;EAX = TEB.ProcessEnvironmentBlock

movzx eax,byte [eax+0x02] ;AL = PEB.BeingDebugged test eax,eax

jnz .debugger_found 由于这些检查很明显,壳一般都会用后面章节将会讨论的垃圾代码或者反—反编译技术进行混淆。 对策

人工将PEB.BeingDebugged标志置0可轻易躲过这个检测。在数据窗口中Ctrl+G(前往表达式)输入fs:[30],可以在OllyDbg中查看PEB数据。 另外Ollyscript命令\可以补丁这个标志。 dbh

最后,Olly Advanced3 插件有置BeingDebugged标志为0的选项。

2.2 PEB.NtGlobalFlag , Heap.HeapFlags, Heap.ForceFlags

PEB.NtGlobalFlag PEB另一个成员被称作NtGlobalFlag(偏移0x68),壳也通过它来检测程序是否用调试器加载。通常程序没有被调试时,NtGlobalFlag成员值为0,如果进程被调试这个成员通常值为0x70(代表下述标志被设置): FLG_HEAP_ENABLE_TAIL_CHECK(0X10) FLG_HEAP_ENABLE_FREE_CHECK(0X20) FLG_HEAP_VALIDATE_PARAMETERS(0X40)

这些标志是在ntdll!LdrpInitializeExecutionOptions()里设置的。请注意PEB.NtGlobalFlag的默认值可以通过gflags.exe工具或者在注册表以下位置创建条目来修改:

HKLM\\Software\\Microsoft\\Windows Nt\\CurrentVersion\\Image File Execution Options Heap Flags 由于NtGlobalFlag标志的设置,堆也会打开几个标志,这个变化可以在ntdll!RtlCreateHeap()里观测到。通常情况下为进程创建的第一个堆会将其Flags和ForceFlags4分别设为0x02(HEAP_GROWABLE)和0 。然而当进程被调试时,这两个标志通常被设为0x50000062(取决于NtGlobalFlag)和

0x40000060(等于Flags AND 0x6001007D)。默认情况下当一个被调试的进程创建堆时下列附加的堆标志将被设置: HEAP_TAIL_CHECKING_ENABLED(0X20)

HEAP_FREE_CHECKING_ENABLED(0X40) 示例

下面的示例代码检查PEB.NtGlobalFlag是否等于0,为进程创建的第一个堆是否设置了附加标志(PEB.ProcessHeap): ;ebx = PEB

Mov ebx,[fs:0x30]

;Check if PEB.NtGlobalFlag != 0 Cmp dword [ebx+0x68],0 jne .debugger_found

;eax = PEB.ProcessHeap

Mov eax,[ebx+0x18]

;Check PEB.ProcessHeap.Flags

Cmp dword [eax+0x0c],2 jne .debugger_found

;Check PEB.ProcessHeap.ForceFlags Cmp dword [eax+0x10],0 jne .debugger_found 对策

可以将 PEB.NtGlobalFlag和PEB.HeapProcess标志补丁为进程未被调试时的相应值。下面是一个补丁上述标志的ollyscript示例: Var peb

var patch_addr var process_heap

//retrieve PEB via a hardcoded TEB address( first thread: 0x7ffde000) Mov peb,[7ffde000+30]

//patch PEB.NtGlobalFlag

Lea patch_addr,[peb+68] mov [patch_addr],0

//patch PEB.ProcessHeap.Flags/ForceFlags Mov process_heap,[peb+18]

lea patch_addr,[process_heap+0c] mov [patch_addr],2

lea patch_addr,[process_heap+10] mov [patch_addr],0

同样地Olly Advanced插件有设置PEB.NtGlobalFlag和PEB.ProcessHeap的选项。

2.3 DebugPort:

CheckRemoteDebuggerPresent()/NtQueryInformationProcess()

Kernel32!CheckRemoteDebuggerPresent()是另一个可以用于确定是否有调试器被附加到进程的API。这个API内部调用了

ntdll!NtQueryInformationProcess(),调用时ProcessInformationclass参数为ProcessDebugPort(7)。而NtQueryInformationProcess()检索内核结构

EPROCESS5的DebugPort成员。非0的DebugPort成员意味着进程正在被用户模式的调试器调试。如果是这样的话,ProcessInformation 将被置为0xFFFFFFFF ,否则ProcessInformation 将被置为0。

Kernel32!CheckRemoteDebuggerPresent()接受2个参数,第1个参数是进程句柄,第2个参数是一个指向boolean变量的指针,如果进程被调试,该变量将包含TRUE返回值。

BOOL CheckRemoteDebuggerPresent( HANDLE hProcess,

PBOOL pbDebuggerPresent )

ntdll!NtQueryInformationProcess()有5个参数。为了检测调试器的存在,需要将ProcessInformationclass参数设为ProcessDebugPort(7): NTSTATUS NTAPI NtQueryInformationProcess( HANDLE ProcessHandle,

PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation,

ULONG ProcessInformationLength, PULONG ReturnLength ) 示例

下面的例子显示了如何调用CheckRemoteDebuggerPresent()和NtQueryInformationProcess()来检测当前进程是否被调试: ; using Kernel32!CheckRemoteDebuggerPresent() lea eax,[.bDebuggerPresent]

push eax ;pbDebuggerPresent push 0xffffffff ;hProcess call [CheckRemoteDebuggerPresent] cmp dword [.bDebuggerPresent],0 jne .debugger_found

; using ntdll!NtQueryInformationProcess(ProcessDebugPort) lea eax,[.dwReturnLen]

push eax ;ReturnLength

push 4 ;ProcessInformationLength lea eax,[.dwDebugPort]

push eax ;ProcessInformation

push ProcessDebugPort ;ProcessInformationClass(7) push 0xffffffff ;ProcessHandle

call [NtQueryInformationProcess] cmp dword [.dwDebugPort],0 jne .debugger_found 对策

一种方法是在NtQueryInformationProcess()返回的地方设置断点,当这个断点被断下来后,将ProcessInformation 补丁为0。 下面是自动执行这个方法的ollyscript示例:

var bp_NtQueryInformationProcess

// set a breakpoint handler

eob bp_handler_NtQueryInformationProcess

// set a breakpoint where NtQueryInformationProcess returns gpa \find $RESULT,#C21400# //retn 14

mov bp_NtQueryInformationProcess,$RESULT bphws bp_NtQueryInformationProcess,\run

bp_handler_NtQueryInformationProcess:

//ProcessInformationClass == ProcessDebugPort? cmp [esp+8],7

jne bp_handler_NtQueryInformationProcess_continue

//patch ProcessInformation to 0 mov patch_addr,[esp+c] mov [patch_addr],0

// clear breakpoint

bphwc bp_NtQueryInformationProcess

bp_handler_NtQueryInformationProcess_continue: run

Olly Advanced插件有一个patch NtQueryInformationProcess()的选项,这个补丁涉及注入一段代码来操纵NtQueryInformationProcess()的返回值。 2.4 Debugger Interrupts

在调试器中步过INT3和INT1指令的时候,由于调试器通常会处理这些调试中断,所以异常处理例程默认情况下将不会被调用,Debugger Interrupts就利用了这个事实。这样壳可以在异常处理例程中设置标志,通过INT指令后如果这些标志没有被设置则意味着进程正在被调试。另外,kernel32!DebugBreak()内部是调用了INT3来实现的,有些壳也会使用这个API。 示例

这个例子在异常处理例程中设置EAX的值为0xFFFFFFFF(通过CONTEXT6记录)以此来判断异常处理例程是否被调用: ; set exception handler

push .exeception_handler push dword [fs:0] mov [fs:0],esp

;reset flag(EAX) invoke int3 xor eax,eax int3

;restore exception handler pop dword [fs:0] add esp,4

; check if the flag had been set test eax,eax

je .debugger_found :::

.exeception_handler: ;EAX = ContextRecord

mov eax,[esp+0xc] ;set flag (ContextRecord.EAX)

mov dword [eax+0xb0],0xffffffff ;set ContextRecord.EIP

inc dword [eax+0xb8] xor eax,eax retn 对策

由于调试中断而导致执行停止时,在OllyDbg中识别出异常处理例程(通过视图->SEH链)并下断点,然后Shift+F9将调试中断/异常传递给异常处理例程,最终异常处理例程中的断点会断下来,这时就可以跟踪了。 另一个方法是允许调试中断自动地传递给异常处理例程。在OllyDbg中可以通过 选项-> 调试选项 -> 异常 -> 忽略下列异常 选项卡中钩选\中断\和\单步中断\复选框来完成设置。

2.5 Timing Checks

当进程被调试时,调试器事件处理代码、步过指令等将占用CPU循环。如果相邻指令之间所花费的时间如果大大超出常规,就意味着进程很可能是在被调试,而壳正好利用了这一点。 示例

下面是一个简单的时间检查的例子。在某一段指令的前后用RDTSC指令(Read Time-Stamp Counter)并计算相应的增量。增量值0x200取决于两个RDTSC指令之间的代码执行量。

rdtsc

mov ecx,eax mov ebx,edx

;...more instructions nop

push eax pop eax nop

;...more instructions

;compute delta between RDTSC instructions rdtsc

;Check high order bits cmp edx,ebx

ja .debugger_found ;Check low order bits sub eax,ecx cmp eax,0x200

ja .debugger_found

其它的时间检查手段包括使用kernel32!GetTickCount() API, 或者手工检查位于0x7FFE0000地址的SharedUserData7数据结构的TickCountLow 及TickCountMultiplier 成员。

使用垃圾代码或者其它混淆技术进行隐藏以后,这些时间检查手段尤其是使用RDTSC将会变得难于识别。 对策

一种方法就是找出时间检查代码的确切位置,避免步过这些代码。逆向分析人员可以在增量比较代码之前下断然后用 运行 代替 步过 直到断点断下来。另外也可以下GetTickCount()断点以确定这个API在什么地方被调用或者用来修改其返回值。

Olly Advanced采用另一种方法——它安装了一个内核模式驱动程序做以下工作:

1 设置控制寄存器CR48中的时间戳禁止位(TSD),当这个位被设置后如果RDTSC指令在非Ring0下执行将会触发一个通用保护异常(GP)。

2 中断描述表(IDT)被设置以挂钩GP异常并且RTDSC的执行被过滤。如果是由于RDTSC指令引发的GP,那么仅仅将前次调用返回的时间戳加1。 值得注意的是上面讨论的驱动可能会导致系统不稳定,应该始终在非生产机器或虚拟机中进行尝试。

2.6 SeDebugPrivilege

默认情况下进程是没有SeDebugPrivilege权限的。然而进程通过OllyDbg和WinDbg之类的调试器载入的时候,SeDebugPrivilege权限被启用了。这种情况是由于调试器本身会调整并启用SeDebugPrivilege权限,当被调试进程加载时

SeDebugPrivilege权限也被继承了。

一些壳通过打开CSRSS.EXE进程间接地使用SeDebugPrivilege确定进程是否被调试。如果能够打开CSRSS.EXE意味着进程启用了SeDebugPrivilege权限,由此可以推断进程正在被调试。这个检查能起作用是因为CSRSS.EXE进程安全描述符只允许SYSTEM访问,但是一旦进程拥有了SeDebugPrivilege权限,就可以忽视安全描述符9而访问其它进程。注意默认情况下这一权限仅仅授予了Administrators组的成员。 示例

下面是SeDebugPrivilege检查的例子: ;query for the PID of CSRSS.EXE call [CsrGetProcessId]

;try to open the CSRSS.EXE process push eax push FALSE

push PROCESS_QUERY_INFORMATION call [OpenProcess]

;if OpenProcess() was successful, ;process is probably being debugged test eax,eax

jnz .debugger_found

这里使用了ntdll!CsrGetProcessId() API获取CSRSS.EXE的PID,但是壳也可能通过手工枚举进程来得到CSRSS.EXE的PID。如果OpenProcess()成功则意味着SeDebugPrivilege权限被启用,这也意味着进程很可能被调试。 对策

一种方法是在ntdll!NtOpenProcess()返回的地方设断点,一旦断下来后,如果传入的是CSRSS.EXE的PID则修改EAX值为0xC0000022(STATUS_ACCESS_DENIED)。

2.7 Parent Process(检测父进程)

通常进程的父进程是explorer.exe(双击执行的情况下),父进程不是

explorer.exe说明程序是由另一个不同的应用程序打开的,这很可能就是程序被调试了。

下面是实现这种检查的一种方法:

1 通过TEB(TEB.ClientId)或者使用GetCurrentProcessId()来检索当前进程的PID

2 用Process32First/Next()得到所有进程的列表,注意explorer.exe的PID(通过PROCESSENTRY32.szExeFile)和通过

PROCESSENTRY32.th32ParentProcessID获得的当前进程的父进程PID

3 如果父进程的PID不是explorer.exe的PID,则目标进程很可能被调试 但是请注意当通过命令行提示符或默认外壳非explorer.exe的情况下启动可执行程序时,这个调试器检查会引起误报。 对策

Olly Advanced提供的方法是让Process32Next()总是返回fail,这样壳的进程枚举代码将会失效,由于进程枚举失效PID检查将会被跳过。这些是通过补丁 kernel32!Process32NextW()的入口代码(将EAX值设为0然后直接返回)实现的。

77E8D1C2 > 33C0 xor eax, eax 77E8D1C4 C3 retn

77E8D1C5 83EC 0C sub esp, 0C

2.8 DebugObject: NtQueryObject() 除了识别进程是否被调试之外,其他的调试器检测技术牵涉到检查系统当中是否有调试器正在运行。

逆向论坛中讨论的一个有趣的方法就是检查DebugObject10类型内核对象的数量。这种方法之所以有效是因为每当一个应用程序被调试的时候,将会为调试对话在内核中创建一个DebugObject类型的对象。

DebugObject的数量可以通过ntdll!NtQueryObject()检索所有对象类型的信息而获得。NtQueryObject接受5个参数,为了查询所有的对象类型,ObjectHandle参数被设为NULL,ObjectInformationClass参数设为ObjectAllTypeInformation(3): NTSTATUS NTAPI NtQueryObject(

HANDLE ObjectHandle,

OBJECT_INFORMATION_CLASS ObjectInformationClass, PVOID ObjectInformation, ULONG Length,

PULONG ResultLength )

这个API返回一个OBJECT_ALL_INFORMATION结构,其中NumberOfObjectsTypes成员为所有的对象类型在ObjectTypeInformation数组中的计数: typedef struct _OBJECT_ALL_INFORMATION{

ULONG NumberOfObjectsTypes; OBJECT_TYPE_INFORMATION ObjectTypeInformation[1]; }

检测例程将遍历拥有如下结构的ObjectTypeInformation数组: typedef struct _OBJECT_TYPE_INFORMATION{ [00] UNICODE_STRING TypeName;

[08] ULONG TotalNumberofHandles; [0C] ULONG TotalNumberofObjects; ...more fields... }

TypeName成员与UNICODE字符串\比较,然后检查

TotalNumberofObjects 或 TotalNumberofHandles 是否为非0值。 对策

与NtQueryInformationProcess()解决方法类似,在NtQueryObject()返回处设断点,然后补丁 返回的OBJECT_ALL_INFORMATION结构,另外

NumberOfObjectsTypes成员可以置为0以防止壳遍历ObjectTypeInformation

数组。可以通过创建一个类似于NtQueryInformationProcess()解决方法的ollyscript脚本来执行这个操作。

类似地,Olly Advanced插件向NtQueryObject() API中注入代码,如果检索的是ObjectAllTypeInformation类型则用0清空整个返回的缓冲区。

2.9 Debugger Window

调试器窗口的存在标志着有调试器正在系统内运行。由于调试器创建的窗口拥有特定类名(OllyDbg的是OLLYDBG,WinDbg的是WinDbgFrameClass),使用user32!FindWindow()或者user32!FindWindowEx()能很容易地识别这些调试器窗口。 示例

下面的示例代码使用FindWindow()查找OllyDbg或WinDbg创建的窗口来识别他们是否正在系统中运行。 push NULL

push .szWindowClassOllyDbg call [FindWindowA] test eax,eax

jnz .debugger_found

push NULL

push .szWindowClassWinDbg call [FindWindowA] test eax,eax

jnz .debugger_found

.szWindowClassOllyDbg db “OLLYDBG”,0

.szWindowClassWinDbg db “WinDbgFrameClass”,0 对策

一种方法是在FindWindow()/FindWindowEx()的入口处设断点,断下来后,改变lpClassName参数的内容,这样API将会返回fail,另一种方法就是直接将返回值设为NULL。

2.10 Debugger Process

另外一种识别系统内是否有调试器正在运行的方法是列出所有的进程,检查进程名是否与调试器(如 OLLYDBG.EXE,windbg.exe等)的相符。实现很直接,利用Process32First/Next()然后检查映像名称是否与调试器相符就行了。

有些壳也会利用kernel32!ReadProcessMemory()读取进程的内存,然后寻找调试器相关的字符串(如”OLLYDBG”)以防止逆向分析人员修改调试器的可执行文件名。一旦发现调试器的存在,壳要么显示一条错误信息,要么默默地退出或者终止调试器进程。 对策

和父进程检查类似,可以通过补丁 kernel32!Process32NextW() 使其总是返回fail值来防止壳枚举进程。

2.11 Device Drivers

检测内核模式的调试器是否活跃于系统中的典型技术是访问他们的设备驱动程序。该技术相当简单,仅涉及调用kernel32!CreateFile()检测内核模式调试器(如SoftICE)使用的那些众所周知的设备名称。 示例

一个简单的检查如下: push NULL push 0

push OPEN_EXISTING push NULL

push FILE_SHARE_READ push GENERIC_READ

push .szDeviceNameNtice call [CreateFileA]

cmp eax,INVALID_HANDLE_VALUE jne .debugger_found

.szDeviceNameNtice db \

某些版本的SoftICE会在设备名称后附加数字导致这种检查失败,逆向论坛中相关的描述是穷举附加的数字直到发现正确的设备名称。新版壳也用设备驱动检测技术检测诸如Regmon和Filemon之类的系统监视程序的存在。 对策

一种简单的方法就是在kernel32!CreateFileW()内设置断点,断下来后,要么操纵FileName参数要么改变其返回值为INVALID_HANDLE_VALUE(0xFFFFFFFF)。 2.12 OllyDbg:Guard Pages 这个检查是针对OllyDbg的,因为它和OllyDbg的内存访问/写入断点特性相关。 除了硬件断点和软件断点外,OllyDbg允许设置一个内存访问/写入断点,这种类型的断点是通过页面保护11来实现的。简单地说,页面保护提供了当应用程序的某块内存被访问时获得通知这样一个途径。

页面保护是通过PAGE_GUARD页面保护修改符来设置的,如果访问的内存地址是受保护页面的一部分,将会产生一个

STATUS_GUARD_PAGE_VIOLATION(0x80000001)异常。如果进程被OllyDbg调试并且受保护的页面被访问,将不会抛出异常,访问将会被当作内存断点来处理,而壳正好利用了这一点。 示例

下面的示例代码中,将会分配一段内存,并将待执行的代码保存在分配的内存中,然后启用页面的PAGE_GUARD属性。接着初始化标设符EAX为0,然后通过执行内存中的代码来引发STATUS_GUARD_PAGE_VIOLATION异常。如果代码在OllyDbg中被调试,因为异常处理例程不会被调用所以标设符将不会改变。 ;set up exception handler

push .exception_handle push dword [fs:0] mov [fs:0],esp

;allocate memory

push PAGE_READWRITE push MEM_COMMIT push 0x1000 push NULL

call [VirtualAlloc] test eax,eax jz .failed

mov [.pAllocatedMem],eax

;store a RETN on the allocated memory mov byte [eax],0xC3

;then set the PAGE_GUARD attribute of the allocated memory lea eax,[.dwOldProtect] push eax

push PAGE_EXECUTE_READ | PAGE_GUARD push 0x1000

push dword [.pAllocatedMem] call [VirtualProtect]

;set marker (EAX) as 0 xor eax,eax

;trigger a STATUS_GUARD_PAGE_VIOLATION exception call [.pAllocatedMem]

;check if marker had not been changed (exception handler not called) test eax,eax

je .debugger_found

.exception_handler ;EAX = CONTEXT record mov eax,[esp+0xC]

;set marker (CONTEXT.EAX) to 0xFFFFFFFF

;to signal that the exception handler was called mov dword [eax+0xb0],0xFFFFFFFF xor eax,eax retn 对策

由于页面保护引发一个异常,逆向分析人员可以故意引发一个异常,这样异常处理例程将会被调用。在示例中,逆向分析人员可以用INT3指令替换掉RETN指令,一旦INT3指令被执行,Shift+F9强制调试器执行异常处理代码。这样当异常处理例程调用后,EAX将被设为正确的值,然后RETN指令将会被执行。

如果异常处理例程里检查异常是否真地是STATUS_GUARD_PAGE_VIOLATION,逆向分析人员可以在异常处理例程中下断点然后修改传入的ExceptionRecord参数,

具体来说就是ExceptionCode, 手工将ExceptionCode设为STATUS_GUARD_PAGE_VIOLATION即可。

3 断点和补丁检测技

术 本节列举了壳最常用的识别软件断点、硬件断点和补丁的方法。

3.1 Software Breakpoint Detection

软件断点是通过修改目标地址代码为0xCC(INT3/Breakpoint Interrupt)来设置的断点。壳通过在受保护的代码段和(或)API函数中扫描字节0xCC来识别软件断点。 示例

检测可能和下面一样简单: cld

mov edi,Protected_Code_Start

mov ecx,Protected_Code_End - Protected_Code_Start mov al,0xcc repne scasb

jz .breakpoint_found

有些壳对比较的字节值作了些运算使得检测变得不明显,例如: if ( byte XOR 0x55 == 0x99 ) then breakpoint found Where: 0x99 == 0xCC XOR 0x55 对策

如果软件断点被发现了逆向分析人员可以使用硬件断点来代替。如果需要在API内部下断,但是壳又检测API内部的断点,逆向分析人员可以在最终被ANSI版API调用的UNICODE版的API下断(如:用LoadLibraryExW代替LoadLibraryA),或者用相应的native API来代替。

3.2 Hardware Breakpoint Detection 另一种断点称之为硬件断点,硬件断点是通过设置名为Dr0到Dr7的调试寄存器12来实现的。Dr0-Dr3包含至多4个断点的地址,Dr6是个标志,它指示哪个断点被触发了,Dr7包含了控制4个硬件断点诸如启用/禁用或者中断于读/写的标志。

由于调试寄存器无法在Ring3下访问,硬件断点的检测需要执行一小段代码。壳利用了含有调试寄存器值的CONTEXT结构,CONTEXT结构可以通过传递给异常处理例程的ContextRecord参数来访问。 示例

这是一段查询调试寄存器的示例代码: ; set up exception handler

push .exception_handler push dword [fs:0] mov [fs:0],esp

;eax will be 0xFFFFFFFF if hardware breakpoints are identified

xor eax,eax

;throw an exception

mov dword [eax],0

;restore exception handler pop dword [fs:0] add esp,4

;test if EAX was updated (breakpoint identified) test eax,eax

jnz .breakpoint_found :::

.exception_handler ;EAX = CONTEXT record mov eax,[esp+0xc]

;check if Debug Registers Context.Dr0-Dr3 is not zero cmp dword [eax+0x04],0 jne .hardware_bp_found cmp dword [eax+0x08],0 jne .hardware_bp_found cmp dword [eax+0x0c],0 jne .hardware_bp_found cmp dword [eax+0x10],0 jne .hardware_bp_found jmp .exception_ret

.hardware_bp_found

;set Context.EAX to signal breakpoint found mov dword [eax+0xb0],0xFFFFFFFF

.exception_ret

;set Context.EIP upon return add dword [eax+0xb8],6 xor eax,eax retn

有些壳也利用调试寄存器的值作为解密密钥的一部分。这些调试寄存器要么初始化为一个特定值要么为0。因此,如果这些调试寄存器被修改,解密将会失败。当解密的代码是受保护的程序或者脱壳代码的一部分的时候,将导致无效指令并造成程序一些意想不到的终止。 对策

如果壳没检测软件断点,逆向分析人员可以尝试使用软件断点,同样OllyDbg的内存读/写断点也可以使用。当逆向分析人员需要设置API断点的时候在

native或者是UNICODE版的API内部设软件断点也是可行的。

3.3 Patching Detection via Code Checksum Calculation 补丁检测技术能识别壳的代码是否被修改(代码被修改则意味着反调试例程已经被禁用了),其次也能识别是否设置了软件断点。补丁检测是通过代码校验来实现的,校验计算包括从简单到复杂的校验和/哈希算法。 示例

下面是一个比较简单的校验和计算的例子: mov esi,Protected_Code_Start

mov ecx,Protected_Code_End - Protected_Code_Start xor eax,eax .checksum_loop

movzx ebx,byte [esi] add eax,ebx rol eax,1 inc esi

loop .checksum_loop

cmp eax,dword [.dwCorrectChecksum] jne .patch_found 对策

如果代码校验例程识别出了软件断点,可以用硬件断点来代替。如果校验例程识别出了代码补丁,逆向分析人员可以通过在补丁地址设置内存访问断点来定位校验例程所在,一旦发现了校验例程,可以修改校验和为预期的值或者在比较失败后修改适当的标志。

http://hi.http://www.wodefanwen.com//hackbruce/blog/category/??±àó???

本文来源:https://www.bwwdw.com/article/l8dw.html

Top