本文参与i春秋社区原创文章奖励计划,未经许可禁止转载!
目录
----------------------理论基础篇-------------------
从科幻小说说起:
危险的潘多拉盒子
来说说应用程序编程接口
工欲善其事必先利其器
(*)正确地作死
(*)被劫持的应用程序接口
(***)Hook与QQ密码的战争
(***)DLL注入与Hook
1:被篡改的名单,导入表注入
2 被劫持的OEP
3.不怀好意的插班生,远程注入
(**)基于inline Hook的行为监视PeDoll
(**)调戏木马病毒前的准备工作
----------------------实战正式篇-------------------
第一章:使用PEDoll调戏cmd调用类型的锁机程序
第二章:使用PEDoll调戏磁盘锁(MBR)锁机程序
第三章:使用PEDoll调戏钓鱼程序
第四章:栈中的明文陷阱,使用PeDoll堆栈功能获取软件中的注册码
第五章:使用PeDoll调戏远控木马
后记
从科幻小说说起:
如果要对病毒讲故事,显然计算机的从业者或者是程序猿总能搬出一大堆的专业术语,他们从一开始的计算机病毒如何破坏,到计算机病毒如何隐藏自己保护自己复制自己总是津津乐道,但显然的,在开篇的文字中我并不打算说的太深入。因为这些太专业的话题对没有相关基础的业外人士并不特别的友好,它除了让人想打瞌睡和让女神感觉你就是个工科男“屌丝”之外,对科普什么是计算机病毒并没有什么帮助。
不过幸运的是,最初的计算机病毒不是程序猿写的,它的出身相当的有文艺范儿,1977年,一个叫雷恩的小说家虚构了一个叫《P-1的青春》的科幻小说,小说中讲述了一种计算机病毒,它能够从第一台传染到另一台,最终控制所有的电脑,当然在那个年代,砖家和叫兽们都认为这是瞎扯淡。
可惜在1988年仅仅过了十年,美国国防部和各大学的6000台计算机,因为一名计算机专业学生编制的计算机病毒而歇菜,显然这一回,砖家和叫兽的脸被打的不轻
那么问题来了,这名学生编写计算机的技巧是哪来的,学编写计算机病毒,哪家强?可惜那年蓝翔还不存在,否者他们会在挖掘机上移植个病毒,让你见识下这玩意有多厉害,但那些年,没有培训班,却流行个游戏叫磁芯大战(Core War)。
正如其名,这游戏在电脑上玩,为了尽量避免涉及更多的专业术语而让文章读起来更有趣味性,我打个并不完全恰当的比方这有点像“养蛊”,有些人相信找来个坛子在坛子里放上各种毒虫,几周以后,坛中的毒虫互相残杀,最后留下来的那只就是所谓的蛊,我不知道这样养出来的玩意是否真有某些神奇功效,但这和磁芯大战确实有不少的相似之处,首先计算机的内存是有限的,你需要做的就是,让你的程序尽量占满这个内存区域,同时避免对对面干掉,当你最终终结了对手把对手从内存中抹除,并且自己占据在内存中,你就获得了胜利。
当然,玩的人多了,花样品种就变的多了,程序猿们开发出了爬行者(Cerrper),侏儒(Dwarf),印普(Imp)…投入这场虚拟的战争中,这看起来有点像生化危机或者是终结者里的场景,爬行者每次运行都会复制自身,很快计算机中就全是密密麻麻的爬行者了,而侏儒重在破坏对手,它的理念是,干掉对手就是存活,最有特色的应该是印普这玩意,它只有一条指令,就是把自己的程序复制到下面一条,这样既能让自己不断复制,还有机会干掉对方。磁性大战显然在那个年代的计算机领域相当的流行,就连比尔盖茨也热衷于玩这款游戏,可惜的是,这款游戏的好玩程度似乎不足以让他留在大学,大二,“大学就是个没点屌用的地方”,比尔盖茨这样抱怨着,然后就退学了。不管怎么说,磁芯大战给了一群程序猿们足够的灵感,千千万万的程序猿为了在自己或者是别人的电脑上留下“到此一游”的印记而绞尽脑汁,那么,在计算机普及到民用的年代,他们的机会来了!。
危险的潘多拉盒子
当然我不能在整篇文章就讲故事,否者我就该把这篇文章发表在“科幻天地”或者是“xxx的人物传记”了,很多人对计算机病毒抱有一种恐惧的心态,我喜欢把它比喻成潘多拉盒子,尽管它外表精巧,盒子的制作者也总是想方设法让你去打开它,但假如你不去打开这个盒子,潘多拉盒子和你家里的饼干盒并没有什么不同,(当然假如你不小心打开了它,而又不了解它,那你只能听天由命了,正所谓不作死就不会死),计算机病毒也一样,归根结底它也是计算机程序,没有运行的话,它也只是存储在你电脑上的一堆数据,排除掉一些操作系统的设计缺陷外(例如以前lnk文件解析中缺乏对路径长度的检查而造成的溢出,当然现在存在这些问题的可能性几乎没有了),他最多占据你一些小小的磁盘空间,当然为了保险起见避免某天手抖运行了它,最保险的应该是把他从你的磁盘中也删了。
当然大部分对计算机并不感冒的人总是希望多一事不如少一事,所以他们巴不得不再有人去设计病毒程序,就算设计了病毒程序也别影响到自己的电脑上,正所谓如果有人让你挑个地方长痔疮,大多人都会挑长在别人身上,但是一些狂热的计算机爱好者或者是恶意程序分析师都喜欢收集大批的计算机病毒,在他们看来,这些木马病毒就像是设计精巧的宝箱一样,在一个可控的环境中窥探它们,总是能获取到更多的知识与技巧,这说起来有点像魔窟探险,尽管里面拥有各种各样的妖魔鬼怪甚至处理不当能让你的电脑领便当,但是当它们最终分析出当中的逻辑与技巧,这感觉就是像征服了恶龙的勇士一样,带着荣耀打开潜藏在宝库中的宝箱。
我想这也就是本篇文章最终行文的目的,我希望有更多的人能够用更加简单的方式享受到这种乐趣。为此,我逛遍了各大大小小所谓的“hacker”群(大多是中二病),在各类网盘和网站中下载了一堆似乎是老司机留下的福利。当然经过筛选,我将选出一些比较有代表性的恶意程序甚至是自己编写一些恶意程序作为分析样本。在阅读完全文后,我希望及是是一些刚进门不就的新手,不懂od,不懂x86,不懂编程,同样也能够使用给出的工具享受这种乐趣。
当然,接下来的内容就可能让一些非计算机相关人士感受到不适,那么我做了一些折中的选择,假如你喜欢看我讲故事而不希望了解当中的技术细节那么你可以挑选那些你感兴趣的东西看,这并不影响你对全文的阅读体验。如果你希望了解更多的相关细节,你可能需要通过MSDN Google baidu甚至是stackover flow,github(俗称gayhub,世界上最大的同性交友网站)来获取更多的相关知识。
我将会在文中标注出内容的深度,(*)表示这是一个老少咸宜的内容,你不需要什么基础就能够轻松读懂,(**)可能表示你需要基本的计算机知识与基础的编程技巧,(***)表示以下的内容可能会更深一步,需要你在相关的领域有更深入的了解。当然在这个难度阶级以上,即时你不阅读,也不影响你理解全文。(*x)则表示,这是需要查阅相关文档例如User Manual,DataSheet,即时是我本人也是在相关文档的帮助下行文,如果你希望打破砂锅问到底的话,你可以查阅之后标注出来的reference documents。
当然如果你是十几年的码神老妖怪或者是常年混迹在52pojie甚至China UNIX的远古飙车老司机,你就可以想方设法在文中找找茬了,本人即非圣贤也非全才,文中疏漏,仍望各位指正。
来说说应用程序编程接口
在码农界广泛流传着一个黄段子,说的是两只程序猿去海边钓鱼,其中一个程序猿居然钓上来了一只美人鱼,这个美人鱼,上半身是人,下半身是鱼,于是这个程序猿就把它给放了,另一个程序猿问:Why?,答:没有API。
这是个略带点专业性质的笑话,当中所说到的API(Application Programming Interface)也就是我们中文所说的应用程序编程接口,当然专业人士看到自然蛤蛤大笑起来,老司机自然也能够领会当中的一二。
那么到底什么是API呢,打个比方就像微波炉,你需要加热食物,你只需要把食物放进微波炉然后告诉微波炉加热多少时间然后摁下启动键就可以了,至于到底微波炉的工作原理是什么,如何防止辐射,需要耗多少电,你一概不需要知道。API就像这个微波炉一样,你只需要给出你的加热时间(参数),你就能得到你想要的热腾腾的食物(结果)
当然上述这个比方并不是那么的准确,我更喜欢使用数学公式来表达这个API,假设我们现在回到了初中,继续来学习那个求圆的面积公式
图(1.1)
没错,就是这玩意(图1.1),S等于PI-R的平方,也就是PI乘以面积,当然这个方程我们往往这么来写
图(1.2)
是的,我们把圆的面积公式定义为f(r),这个函数接收一个参数r也就是圆的半径,现在不如让我们假设这个求圆面积公式是个机密,我们不想让别人知道,圆的面积是怎么求的,这样我们就可以开个公司叫《专业求圆面积有限公司》,我们只告诉外界我们有一个叫f(r)的函数,就是你只需把这个圆的半径告诉我们,我们就可以把圆的面积告诉给你,不要998现价求一次只要188,这样客户蜂拥而至,给出我们f(1),f(2),f(3),我们马上给出答案,面积分别约是3.14,6.28,9.42
当然,最准确的说法应该是用C语言了
float __stdcall getAreaofCircle(float r)
{
Return 3.14f*r*r;
}
我们将代码编译为DLL,别人就无法直接知道我们到底是怎么求出圆的面积了,而别人需要使用我们的DLL的话,也只需要导入float __stdcall getAreaofCircle(float r)这个函数声明然后把参数传递进来,就可以得出结果了,这就是API。简单来说,它就是一个没必要告诉我们怎么实现,只告诉我们如何调用的函数。
那么问题又来了,API能做什么,实际上我们在使用电脑的过程中,无时不刻在调用着这些API函数,例如在桌面上创建文件,我们就调用了创建文件的API,在QQ上发送消息,就调用了网络发送数据的相关API,修改注册表,就调用了修改注册表相关的API。
实际上你可以打开你的系统盘,找到windows目录下的system32中的kernel32.dll,user32.dll,winmm.dll,ntdll.dll……当中就包含着海量我们经常调用的API,我们使用PeLord来查看它的导出表,能看到一大堆的API函数,例如在User32.dll中的MessageBoxA,就是一个弹窗的API,假如你在桌面上看到一个弹窗告诉你哪里哪里又错了,那么基本是它就是调用了这个API函数
API函数 |
作用 |
备注 |
RegOpenKeyExA |
打开一个注册表以便操作 |
|
RegSetKeyValueA |
设置注册表键值 |
|
RegsetValueExW |
同上 |
Unicode版本 |
RegsetValueExA |
同上 |
|
RegSetValueA |
同上 |
|
RegCreateKeyA |
创建一个注册表键值 |
|
FindWindowExA |
查找窗口并取得句柄 |
|
FindWindowExW |
同上 |
Unicode版本 |
RegCreateKeyExW |
创建一个注册表键值 |
Unicode版本 |
CreateFileA |
创建/打开一个文件(或设备,管道) |
|
CreateFileW |
同上 |
Unicode版本 |
CreateProcessInternalW |
创建进程 |
未公开API,unicode版本 |
CreateProcessW |
同上 |
Unicode版本 |
CreateProcessA |
同上 |
|
connect |
Socket 的TCP网络连接 |
|
send |
Socket发送TCP数据包 |
|
sendto |
Socket发送UDP数据包 |
|
recv |
TCP接收数据包 |
|
recvfrom |
UDP接收数据包 |
|
DeleteFileA |
删除文件 |
|
DeleteFileW |
同上 |
Unicode版本 |
ExitWindow** |
关闭计算机 |
|
TerminateProcess |
结束进程 |
|
Process32FirstW |
枚举进程 |
Unicode版本 |
Process32NextW |
同上(下一步操作) |
Unicode版本 |
OpenProcess |
打开进程 |
|
LoadLibraryExW |
加载链接库(常常是dll) |
Unicode版本 |
CreateWindowExW |
创建窗口 |
Unicode版本 |
SetWindowPos |
设置窗口位置 |
|
SetFileAttributesA |
设置文件属性 |
|
SetFileAttributesW |
同上 |
Unicode版本 |
SetWindowTextA |
设置窗口文本 |
|
SetWindowTextW |
同上 |
Unicode版本 |
倘若我们使用C语言写出来,那么大致会是这个样子(附件A01.zip):
无需怀疑,它一定是调用了User32.dll中的MessageBoxA(当然也有MessageBoxW,unicode版本的API,不深入讨论)。
那么现在有个问题,既然有那么多的API函数,我应该如何找到我想要的API函数呢,比如创建文件,我该哪里找,当然,本文主要讨论的都是windows系统下的相关知识,因为,window民用的PC端用户群庞大,因此xp年代个人电脑十有八九都是毒窝,幸好windows的开发商Microsoft除了坑钱外技术文档也是做得相当不错的,你可以在MSDN上,找到绝大部分开放的API函数。在这里,我列举一些在木马病毒中经常看得到的API函数
扩展阅读,当我们使用计算机觉得它很简单时,往往都是无数的大犇前辈不懈的努力让它看起来觉得很简单,我们调用一个API,在屏幕上显示一个窗口,播放一段音乐,显示一张图片,我们觉得理所当然,并且洋洋自得“这个程序是我写的”,假设我们没有这些API,我们该怎么办,我们还能够理所当然的哪怕创建出一个TXT文件么?
遗憾的是这个问题马上会上升到文件系统甚至是物理层面的电流脉冲上来,例如我们希望使用STM32系列ARM芯片去读取SD卡的文本文件,我们很可能就需要熟悉SPI或者是SDIO协议,然后在此基础上实现FAT32文件系统,要做到功能与性能的实现并不轻松。
还好,制作操作系统的程序猿们把这些底层的操作封装到了“硬件抽象层”(HAL)之外,从而让我们这些用户层的程序猿,能够把注意力放在如何读取文件写文件上来,而不是关心我们的文件系统是NTFS,YAFFS,FAT还是我们的数据会写在硬盘上还是SD卡上。
工欲善其事必先利其器
如果说起恶意程序逆向分析,不管是熟悉的还是不熟悉的,总是喜欢把调试器打开并加载,在一堆CALL中越跟越深,新手往往就迷失在这无穷无尽的汇编代码中,一些稍微有些反逆向基础的代码编写者,假如不打算使用一些壳技术来保护自己代码的话,在代码中检查调试器,并且在恰当的地方设定一些暗桩,甚至自己编写脚本解析引擎(Scripts Virtual Machine)总是能给逆向分析者制造或多或少的一堆麻烦,但是更多的时候,作为分析者的我们也在思考,真的有必要这么做么,不管我们使用ollydbg这类的动态调试器,还是IDA这类静态调试器,甚至使用windbg调试ring0(内核)程序,我们分析代码的目的,绝大部分时候是希望获得这个程序到底做了什么功能,而不是这个程序怎么实现了这个功能,因此从准备写这篇文章开始,我就决定,这篇文章中,要做到No OllyDbg,No IDA,No debugger,把我们的注意力,放在如何知道这个程序做了什么,而不是洋洋洒洒的对x86汇编和各种调试器调试技术侃侃而谈。
幸运的是,随着时代的发展,用计算机写程序的门槛变得越来越低了,从最初的在卡片上打孔机到后来的386,直到今天甚至我们说某些方面的性能过剩,强大的计算机性能为一些更为简单的语言语法打下了基础,我们更加关心如何实现这个功能,而不是这个功能会花掉我们多少的内存和多少的CPU执行周期(空间复杂度,时间复杂度),以至于诞生了Java,python,ruby….这一类更加关注开发效率更加“内存管理安全”类的语言,甚至有投机者专门为那些不学英文的开发者开发了“易语言”这类的伪编程语言,(在从前的《程序猿鄙视链》一直出于鄙视链低端的HTML或是BASIC终于也因此找到了优越感),于是不管是刚学完一元二次函数的初中生或者是英语不及格根本不知道内存为何物的中二病也加入到了这个编程的大军中,但他们中的大部分,大多处于荷尔蒙与肾上腺素蓬勃生产的阶段,他们总更喜欢破坏多于创造,喜欢给别人制造点麻烦,而不是给他人点方便。于是在这个时期,各类锁机,木马,钓鱼程序便孕育而生,同时恶意程序的逻辑往往也变得更加的单纯与易于分析,当然,这就给我了写这篇文章的机会,我也能愉快的借此机会好好地吹吹牛逼。
那么,最快速分析恶意程序的手段是什么,显然的,直接运行恶意程序,观察它会对我们电脑造成什么影响是最直观的,当然这种风险也是最直观的,比较善良的也许只是弹弹窗霸占你的屏幕,腹黑的也许会偷偷打开你的摄像头监视你的键盘,或者偷取你的一些文件,甚至加密你的文件并且告诉你想要解开你必须支付一笔钱财。更狠的会格式化你的磁盘,甚至在早期的计算机中可能烧毁硬件(例如大名鼎鼎的CIH是如何一步一步蚕食你的硬盘的),这个直接运行显然不是那么保险,但幸好随着虚拟化技术的发展我们有了虚拟机,当我们把恶意程序放在虚拟机中运行,无论它怎么兴风作浪,当我们把虚拟机关掉,它无法对我们的宿主机造成任何的影响(当然也存在虚拟机逃逸的范例,但这往往是高度硬件相关的,如果有人为了整蛊你而特意做了这样一个程序,你该感到荣幸),所以,请允许我在接下来一个小小的篇幅中,介绍如何安装并使用虚拟机。
(1)下载虚拟机安装程序:你可以在这个网页中下载到虚拟机的安装程序
https://pan.baidu.com/s/1i5HOx73
然后你可以去百度一个注册码,或者上某宝购买一个。
(2)同时你需要一个Windows的镜像文件,你可以在msdn tellyou网站中找到,也可以复制下面的地址下载一个
cn_windows_7_ultimate_x64_dvd_x15-66043.iso (3.11 GB)
(3)虚拟机下载完成后,解压安装就行了,如果你不清楚,直接点击下一步完成安装。
(4)那么最关键的是,是如何在虚拟机中安装windows,我们打开虚拟机,并选择创建新的虚拟机:
(5)点击下一步
(6)选择安装程序光盘映像文件,然后把我们下载下的windows映像iso文件选上
(7)下一步,弹窗告诉我们需要密钥,我们选是就行了
(8)选择虚拟机的安装路径,我建议选择一个容量较大的磁盘
(9)只是做分析的话,20GB够了
(10)点击完成,剩下就是安装windows了
(11)安装完成后点击“开启此虚拟机”,就可以打开这个虚拟机了
(12)如图所示
(13)在安装完虚拟机后,拍摄快照,以便于在调试分析后恢复虚拟机的状态
正确地作死
在我们找到一个恶意程序之前,在虚拟机中安装VMTools,这样我们就可以直接将宿主机中的恶意程序拷贝到我们的虚拟机当中,在这里我已经安装好了VMTools,如果你没用安装这个工具,在这里应该会有一个提示你安装的菜单。如图
接下来,我们拷贝一个恶意程序到我们的虚拟机当中,并尝试运行它,在这里,我拷贝了一个锁机程序,当然,为了让这个程序更好的破坏我们的虚拟机,我们还需要以管理员身份去执行这个程序,很快,虚拟机就黑屏重启了,同时成功锁死了我们的虚拟机。
这样,假设我们不知道这个程序是否是恶意程序的话,在虚拟机中运行这个程序,常常就能让它原形毕露,从而避免恶意程序直接对我们的宿主机造成伤害。而恢复我们的虚拟机方法也非常简便,我们只需要将快照恢复到初始状态就可以了。
被劫持的应用程序接口
说到劫持(hijack)当然这里要说的不是劫机这种大买卖,这里说的劫持,归结起来有点像碟中谍中私下偷偷摸摸的信息传递,或者说非常像狸猫换太子。实际上在历史上人类总是热衷于这种勾当,打个恰当的比方,你希望给你朋友写一封信,信件中说你想要向朋友李四要一部《比利海灵顿的哲学》作为业余的人体艺术鉴赏课程,这个信件是张三帮你传递的,正常情况下,张三会帮你把这个信件交到李四手上,但是某天有个王五,他希望在你身上搞些坏主意,他知道你经常给李四写信,所以某一天,在你交给张三一份信后,他就可以实施他的计划了
在张三的水里下蒙汗药,或者把张三灌醉,然后在张三神魂颠倒的时候,偷偷拆开信件。他可以看了内容,再原封不动的把信件放回去,也可以把《比利海灵顿的哲学》换成《苍老师作品集》,甚至他可以撕毁信件然后给张三一棍子。
不论怎么样,王五通过这个手法,知道了你是一个基佬,而李四到底能收到什么信件,甚至根本收不收的到信件,已经不再是你能决定的了。在信件投递出来的那一个,你对信件到底怎么到李四手上的过程一无所知。
我打这个比方,是想说明他和我们计算机当中的Hijack非常的相像,程序也是数据,在执行期间,它们大多情况下也会被加载到内存当中,我们只要在内存中篡改这个程序,让他在执行功能之前,先向我们汇报一下,我们就能决定,这个功能到底执行不执行,甚至篡改它执行。
现在回到我们之前所说的应用程序接口这个知识点上来,在windows的开发过程中,我们很多情况下,也是通过使用这些用程序编程接口(API)来完成大多数的功能,之前说过了,API说到底,也是被加载进内存的程序,我们只需要监视这些API,就能够知道,这个程序到底在执行过程中做了哪些小动作
比如在哪创建了文件,打开访问了哪些文件,动了哪些注册表,使用网络向哪些IP地址发送了哪些数据,是的,通过劫持技术,我们都能知道。
Hook与QQ密码的战争
WARNING本章节可能引起新手朋友的不适,请选择性阅读
钩子技术(Hook)的年代久远,这是一个重要实用而且被玩烂了的技术,当然现在说到Hook技术,经常会扯到一些旁门左道上,但Hook技术确确实实是windows最为重要的一环,从内核(ring0)层面上的过滤驱动程序(Filter Driver)到用户层面(ring3)上的输入法,热补丁…无不与Hook技术息息相关,Hook不仅仅是某种特定的代码或功能呢,它更像是一种截获信息的统称,没有Hook,那么输入法,杀毒软件,都不会存在。
在深入介绍HOOK技术时,我想先说一个非常有趣的对抗故事,它说的是一个关于截获QQ密码与对抗的故事。
在2000年左右,在那个还没有360全家桶和腾讯全家桶的win98年代,QQ大概就像上面这个样子那么单纯可爱,当时,密码保护机制不是那么的完善,而Windows driver model(WDM)也在众多猴子的千呼万唤之下刚刚发布,因此更不用谈ring0层的保护了。
当然你在用户口令上输入密码,显示的仍然是*字,但是我们只需要用点小工具,很容易就能够把密码给弄出来了,没错,这玩意大家都很熟悉,它叫spy++,翻译过来也是间谍的意思,实际上它不是某个hacker大佬开发出来的,他就是微软为它VC++6.0这个IDE发布的一个工具,即使是在今天,你仍然可以在visual studio里发现它的身影。
这款工具的使用非常简单,你只需要打开它
然后把“查找程序工具”右边的十字移动到你需要监视的窗口上就可以了。这里我选择一个记事本为例
可以看到,SPY++成功获取到了记事本上的文本。
实际上这个过程调用了一个API函数,叫GetWindowText(A/W)意思就是取得窗口的文本,实际上windows里的窗口都有一个句柄,当中的文本框,标签框甚至是按钮,都拥有自己的句柄,这个函数的原型大概是这个样子
int WINAPI GetWindowText(_In_ HWND hWnd,_Out_ LPTSTR lpString,_In_ int nMaxCount);
这个函数就能通过句柄来获取这个窗口的文本,那么问题来了,这个问题同样出现在了QQ上,你只需要把这个十字箭头移动到QQ上,SPY++显示的文本将会是密码的明文,当然,有人输完QQ的密码然后离开电脑,黑客再偷偷跑到他电脑上打开spy++再获取明文密码这种情况比较少,但是,要是不怀好意之人,在电脑上偷偷安装一个程序,不断使用GetWindowText去获取qq对话框上的账号密码上的文本,然后再使用smtp之类的方式把qq账号密码发送到自己邮箱的话,结果就不一样了,实际上,在最早期的时候,大部分qq的盗号木马都采用了这种方式。不过幸运的是,这种办法到了windows2000就彻底实效了。
不过这并不影响盗号者和盗号木马作者们的热情,我们继续回到SPY++,仍然把十字箭头放到记事本上,同时在上面的消息菜单上,把键盘消息打个√,然后点击确定:
然后我们回到记事本,按下回车然后输入文本“123”
切换回SPY++,然后你发现了什么
没错,SPY++截获了我们在记事本上输入的信息,可以看到从WM_KEYDOWN上,SPY++告诉了我们:
我们按下了一个回车(VK_RETURN),1(VK_NUMPAD1),2(VK_NUMPAD2),3(VK_NUMPAD3)
所有的按键记录都被记录了下来,实际上,其他的窗口也是类似的情况,Windows拥有一套我们称之为“消息循环”的管理机制,当我们使用键盘按下一个按键之后,它最终会转换为消息,分派到活动的窗口上,在这里,我们监视了两个消息,一个是WM_CHAR,一个是WM_KEYDOWN,显然,通过对这个消息循环的hook,我们很容易就截获到了键盘输入。这样,我们就能在密码到达密码框之前截获密码的明文。那么,一个新的盗号木马制作方案诞生了,实际上到今天为止,绝大部分的远控的键盘记录功能和少部分的盗号木马仍然使用这种原始的消息Hook方案来窃取用户的键盘输入,并企图在当中截获密码。
终于,在QQ账号开始变得值钱之后,企鹅开始重视起盗号木马猖獗的这个问题,他们终于把这个键盘记录的战争上升到了内核层面上来,大约在05年的时候,企鹅向棒子购买了驱动层(ring0)的保护,驱动层比windows消息更加的底层,但因为需要安装驱动程序,并且这个驱动程序做的相当的底层直到中断层(0x93中断服务),结果导致了一堆兼容性问题,但是,它确实也干掉了绝大多部分的盗号木马,一时间,qq的密码不再变得那么唾手可得了。
但是道高一尺,魔高一丈,如果玩过51兼容芯片或者ARM写裸机程序的朋友应该很清楚,芯片都有中断,当触发一个硬件中断时(例如一个上升沿脉冲中断),CPU将会跳转到中断代码,而中断代码的跳转地址,是一个表来维护的。我们管这玩意叫中断描述符表(IDT),在IOAIPC出现以后,我们就可以修改这个中断对应的关系,这就是说我们只需要修改IDT中的跳转地址,我们就能比棒子的驱动更先一步hook到键盘的输入数据从而截获密码。
这个谁走的更加底层,谁先Hook到键盘数据的战争一直持续着,直到windows7以后,微软再也看不下去了,于是他规定想进内核,必须提供数字证书(不仅不便宜而且需要审核资质)。
于是,盗号者的目光变成了如何做一些比较有诱惑性的钓鱼界面,来骗取用户输入而盗取qq密码了,这个即原始又没啥技术含量但运气好时确实也有效的手段,一直沿用到了今天。
(更多细节请参考:《寒江独钓windows内核安全编程》)
DLL注入与Hook
WARNING本章节可能引起新手朋友的不适,请选择性阅读
显然,为了行为监视,作为一个学生狗花个5k块去买数字证书是划不来的,何况目前微软的代码数字签名并不出售给个人用户。
不过即便如此,我们仍然有非常多的工具可供选择,例如现在非常流行的在线分析系统火眼和哈勃
上图就是火眼在线分析系统的结果,在线分析拥有其非常多的优势,操作简单报告也通俗易懂,然而,其弊端也非常的明显,它的分析与被分析的程序无法进行任何的交互,所以它的分析报告常常无法那么全面。
当然PC端我们还有PCHunter,这款软件的前身是Xuetr,是由52老司机linxer开发完成的,当时就备受好评,即使是现在,PCHunter也作为各种老司机必备神器,当小白仍然安装着各种全家桶,开机时弹窗着各种屠龙宝刀点击就送,一刀9999,范伟打天下时,老司机早就使用PCHunter来监视自己电脑上不正常的一举一动了。
但即便是我们做不到PCHunter一样搞到数字证书,进而修改SSDT搞搞ring0层面的hook,我们仍然可以在ring3层面上,将API Hook玩的风生水起
这里我们主要讨论的是,如何加载DLL来完成这个Hook过程,DLL一向用于程序的拓展与补丁,它仿佛就是为了插入代码而准备的,当然本篇文章并不准备讨论直接插入二进制代码来完成这个Hook过程,这样会导致一系列问题需要处理,如果你有兴趣,你可以阅读《Windows PE权威指南》《windows 核心编程》这两本书,里面有对这个技术的详细介绍。
在这里,我希望用下面几张图来表示DLL注入到Hook是如何完成的
1、首先这是原始程序
2、然后我们在程序中加载DLL
3、修改的代码,在头部修改为类似于mov eax,xxxx jmp eax之类的跳转代码使他能够跳转到我们的DLL函数中,并保留原始代码
4、执行我们的hook代码,并恢复API的原始代码,最后调用这个API(如果你希望的话)并执行,对于原始程序来说就像这个调用过程一切顺利并且毫无察觉。我们一般管这个过程叫做inline hook。那么现在的重点是如何将我们的DLL插入到目标程序当中。
说起DLL的插入姿势,我想引用生物链最顶端的男人贝尔格里尔斯的一句话“这玩意就像摸鳟鱼一样,每个人都有每个人的办法”,在这里,我将使用C++代码主要讲解目前主流的几种DLL注入姿势。
被篡改的名单,导入表注入
下面的这一幕经常发生在间谍片中玩烂,大致是某个关键人物举办或是参加一场宴会,而特工们需要做的是千方百计去修改宴会的邀请名单,好把自己的名字给加上去,我说这个故事的目的是,在windows的可执行文件(以下特指PE文件)中,也有这种名单,但这个名单邀请的不是名人贵族,而是在可执行文件被加载时,同时被加载的DLL。
那么这个名单在哪里呢?,我们可以打开PE文件分析的利器, LordPe,如果你没有这个工具,你可以上去下载一个:
打开软件,然后点击Pe Editor,在这里,我选择notepad.exe也就是记事本做讲解实例,选择后,点击directiories
找到import tables(导入表),点击“…”
现在,看到导入表的数据了么
没错,这些dll就是这个记事本的邀请名单,当你双击记事本时,这些DLL都将被加载到内存当中(实际上如果有别的程序已经加载过这个dll,加载器就只是把这个dll的内存地址给映射过去只保留一个内存拷贝,这也就是为什么在xp年代,hook了这些dll里的API函数能够实现全局HOOK),而点击一个DLL文件,下面的函数是不是也非常的眼熟,没错,它们当中的大部分都是系统的API函数,正常情况下,这些函数都是在程序中将会被调用到的函数(由一个IAT表来维护这种调用关系,在双桥结构中,还有一个INT),所以在很多情况下,通过对导入表的分析,我们就可以猜到,这个程序大致有没有修改我们的注册表,有没有访问文件,有没有进行网络交互….
当然在本文中并不打算讨论PE文件头的结构细节,这些资料你都可以在《Windows PE权威指南》中查询到,在这里,我打算做一个小小的篡改导入表的实验。
首先打开Visual Studio,写一个最简单的Hello World程序
#include
int main()
{
printf("Hello world");
getchar();
}
同时编写一个最简单的DLL,在DllMain中,我们仅仅只是在DLL Attach也就是dll被加载到程序时,显示一个对话框,弹出“导入表修改实验”,同时我们还需要编写一个导出函数,当然,这个导出函数中我们什么也不做
void DLL_API X_dummy()
{
return;
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
MessageBoxA(NULL,"导入表修改实验","",MB_YESNO);
}
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return (TRUE);
}
最后,编译两个程序并把编译的结果提取出来
我们直接运行Test.exe,显示出hello world一切正常
现在我们打开LordPE,并将TestDLL加入它的导入表中
点击Ok,现在,我们再来执行下Test.exe,可以看到,DLL确实被加载进了程序当中,而且在加载在执行程序前,因此这个时候,Hello World还没有正确显示出来,导入表篡改成功(代码包含于附件A02)
被劫持的OEP
导入表修改简单而且好用,但是之前说过了,DLL注入就像是摸鳟鱼,每个人都有每个人的办法,而且导入表注入同时它的缺陷也是非常的明显,
1,程序中可以非常方便的扫描出导入表是否被篡改,
2.如果PE结构非常的紧凑,你甚至无法插入一个section且没有额外空余的位置来重构导入表,你也将会失败,当然有人会说,是否可以伪造成和源DLL相同的名字来进行导入,这当然也是注入的一种办法,但假设这个DLL的导出函数非常的多,何况你还得知道传参的个数和方式,那么这个问题就变得相当的复杂了。
3.有些程序根本就没有导入表,这并不奇怪,,有些DLL,即便是没有导入表,它一样会被加载到程序当中,我们要做的仅仅只是定位到loadlibrary和getprocessaddress这两个函数,我们就可以把其他的dll一同加载进来,当然如何定位又是另一种“摸鳟鱼”的话题了。
面对导入表修改的问题,说到这里应该先提出另一个问题,在PE加载器加载了一个PE可执行文件后(EXE),它将从哪里开始执行,是的,PE格式文件有多个section,当PE文件被加载时,他们将被映射到内存当中,而且加载的位置由Pe Header中给出建议的值(暂时不讨论重定向,aslr),在以前尚未对PE格式有所认识时,也许很多人认为,程序就是从文件的一开始开始执行,然而实际上,在Pe Header中,定义了一个OEP(original entry point),在x86中,它是一个32位的值,指的就是程序一开始执行的地址。
在了解了这个之后,注入dll的思路就变得非常清晰了,我们可以直接修改OEP的值,让程序先执行我们加载DLL的代码,然后再跳转回原始OEP继续执行程序,实际上,非常多的壳程序用的几乎都是这一手法,他们将原来的PE数据打包压缩,将OEP重定向到自己的解包代码中,在程序执行后,他们将原始的数据恢复到内存中,这样一来,不仅压缩了程序的体积,而且对执行效率并没有多大影响。
这里不再进行更深入的讨论,详细的资料你仍然可以通过查阅《Windows PE权威指南》《加密与解密》得到。
不怀好意的插班生,远程注入
前面说到的两种注入方式,如果说有什么特别明显的缺陷的话,那就是都会对源文件造成修改,当然,前面两种方式经常把可执行文件搞炸,因为新的技术新的保护方案不断在推出,假设有哪一个方面没考虑到的话,可能源程序就会变得根本没有办法执行下去,总的来说,上面的两种做法,经常导致程序崩溃,显得不那么的“官方”。
那么有没有办法更加的“官方”,甚至能在程序执行期间,也能够将我们的DLL插入进去实现Hook,那么这里我介绍下大名鼎鼎的CreateRemoteThread这个API函数,这个函数在早期,基本只要是有外挂,木马编写程序的都知道,它的作用就和他的名字一样,创建远程线程,也就是在其他的程序中创建一个线程,执行这个API的步骤基本上是这样
1.在远程进程里申请一段空间
2.拷贝执行的代码到该空间
3.调用CreateRemoteThread执行代码
当然,假如我们只需要加载我们的DLL的话,事情就变得更简单了,我们只需要申请一段远程空间,把需要加载的DLL文件路径给填写进去,然后定位kernel32.dll里的LoadLibraryA(地址都是相同的),之后CreateRemoteThread,把远程地址作为参数传递给LoadLibraryA就行了,这样,我们的DLL就顺利的安插在了目标程序当中。
完成这一过程的代码也非常的简短,同时他拥有非常不错的兼容性,因此,它也常常被用于各种木马病毒隐藏自身的一种手段。
BOOL InjectDll(DWORD dwPID, LPCSTR szDllPath) {
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
HMODULE hMod = NULL;
LPVOID pRemoteBuf = NULL;
DWORD dwBufSize = (DWORD)(strlen(szDllPath) + 1) * sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;
if( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ) {
printf("Could not open target process(%d) error: [%d]", dwPID, GetLastError());
return FALSE;
}
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
hMod = GetModuleHandle("kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf,0, NULL);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
基于inline Hook的行为监视器PeDoll
在介绍了几个章节比较沉重的技术理论话题之后,现在我们开始正式回到如何愉快轻松的,调戏那些恶意程序了。
那么,我先介绍一个软件,它的名字叫PeDoll,他是由笔者开发的,当然这个软件目前并不打算开源,但是,在本章节之后,我将会放出这个软件的release版本(入门版,基础版,专业版)你可以使用论坛的魔法币来下载它们:),当然秉承着越贵越好的原则,专业版提供的功能也会远多于入门版。
那么PEDoll是什么呢,就是这玩意(启动logo也是笔者原创的,你可以说非常中二病,但笔者觉得挺符合主题也挺cooooooooooooooool):
它是一款基于inline hook的行为监视器,就和我们前几章节说的一样,PeDoll通过DLL注入到目标的进程,hook关键的API,从而达到行为分析的目的,当你下载到PeDoll时,它实际上是由三个文件夹组成的。
你可以看到PeDoll中包含三个文件夹
其中,常用脚本中包含了一系列常用的分析脚本,当它们被加载到PeDoll时,一个专门的脚本解析引擎将会检查它们的语法准确性并部署到hook与过滤器(filter),中断规则(interrupt rules),因此如果你没有阅读过相关的帮助文档,不建议对其直接修改
控制器包含了PeDoll 的操作程序界面于控制器
请双击击 PeDoll的分析界面类似这样。
在调试机程序文件夹中,你可以找到下面两个文件
其中,PE.DLL是用于注入的DLL,PeDolls.exe是调试机中与控制器通讯的调试器,它负责将DLL注入到目标程序,或者直接修改目标程序,终止调试进程等任务。
你应该把这两个文件拷贝到你的虚拟机中,并以管理员身份运行PeDolls.exe,如图:
如果没有报错的话,它将会把Pe.DLL拷贝到C:\windows下,这样,调试程序就能够以DLL的默认路径来加载PE.dll,当你完成这步时,PEDoll就算是完成部署了.
调戏木马病毒前的准备工作
当然如果说不需要任何的基础就能够分析那些恶意程序,这显然是不切实际的,因此我才在之前花了大量的笔墨去或浅显或深入的去介绍一些理论知识。
但它确实也没用想象中的那么复杂,也许你没用过od,ida甚至不会编程,这些都没有关系,但前提条件是,你至少需要明白,大部分的恶意程序在我们的电脑上,都可能做一些什么,而他们做的这些事情,又会用到哪些API函数?
在这里,我稍微总结了一些“不大高端”的病毒一些常见的行为
1.大多数的木马程序会将自己写入开机启动,他们常常被写到下面几个注册表位置
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run]
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce]
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices]
当然上面列举的并非全部,实现开机启动的方式也是多种多样。
2.绝大多数的木马程序会涉及到文件操作,他们可能对windows下尤其是system32目录下的文件尤其感兴趣。他们感染一些系统文件或是一些常用软件,来实现传染或者是开机启动
3.相当一部分的木马程序会使用OpenProcess或者与他类似功能的函数,使用这个函数常常被用于创建远程线程将自己注入到别的进程中逃避检查。
4.大部分的锁机程序,会使用CreateProcess这个函数,因为他们篡改windows账户密码的手段很多是通过对cmd.exe传参完成的
5.绝大部分的磁盘锁程序,会使用CreateFile(A/W)去打开设备符\\\\.\\PHYSICALDRIVE0,锁机引导程序肯定是写在第一扇区也就是MBR区,写大小几乎也是512字节,也就是刚刚好一个扇区大小
6.相当一部分的钓鱼程序使用SMTP来将钓到的账号密码发送到邮箱,相当一部分的则使用80端口的TCP连接以POST或GET的方式发送到空间中。还有少部分的自己写服务端,当中极少部分是采用UDP的
7.一些破坏型的木马病毒,常常手段无非是删除文件,格式化硬盘,一部分勒索型还会加密的你的文件。当然,现在能够直接损坏硬件的病毒已经非常少了,基本上重装系统都能解决问题
8.有一小部分的病毒都可能使用某些手段让你计算机重启,因此,ExitWindow**这个API函数好好关注一下还是应该的
当然,无论我在这里说多少理论上的东西,作为一个调戏的教程,都是不合格的,所以在之后的章节中,我们将使用PeDoll现场调戏这一类的恶意程序。同时,如果你发现你编写的恶意程序上榜并恼羞成怒的话,笔者谢绝一切的的跨省,水表及人肉。并不对其他读者对其造成的任何后果负责。
下一篇地址:http://bbs.ichunqiu.com/thread-16846-1-1.html?from=oschina
更多安全技术、精品好文、白帽黑客大佬尽在:http://bbs.ichunqiu.com/portal.php