本帖最后由 root1605 于 2017-8-26 13:38 编辑
Ⅰ预备
以下部分与后文相关,先做一个提前的介绍。
①编译器
在大部分场合,编译器指的是将“高级语言”转换为机器语言(或者中间语言)并生成可执行文件(不一定是.exe)的工具。高级语言指的是像C++,Java,C#之类的语言,其相对于机器语言来说更适合人直接编写程序编译器是将这些语言的代码文件(源代码)转化为计算机可以执行的程序(包含机器码)的工具。不同语言间编译器一般不通用(但比如C语言的源代码大部分可以在C++的编译器上成功编译)。像MinGW,MSVC,Roslyn等都是编译器(它们是编译器,不是开发环境)。
以下是一段VB6的源代码,这些都是“给人理解的”,必须由Visual Basic的编译器生成可执行程序才可以运行。
这里简要介绍以下主要过程。在对源代码做出一些处理后,编译器就先生成汇编码(有些编译器直接生成机器码,如早期的Turbo C)。这样的文件叫做“对象文件”,它还是不能运行,一般编译器接着会把外部函数的代码(Windows下通常是.lib),添加到可执行文件(这个过程叫“链接”),也可能在运行的时候再动态地装载(后文专门介绍,动态链接库文件)。之后再经过一些过程(此处省略),就生成了可执行程序(最终还是得回归到“0101001”)。
②CMD&PowerShell
Windows下的命令提示符(CMD)一直有着重要的作用。它以文本输入输出的模式,提供了一种简洁的访问系统的某些功能的方式。需要注意的是,命令提示符不是DOS虚拟机,只是像DOS的操作界面。在通常情况下,用户会使用图形窗口,但有时使用CMD(命令提示符)显得更加方便快捷。比如,要求定时关机,使用命令提示符更加快捷。只需输入以下命令即可。
编程时合理运用好它也能减少工作量,如:
PowerShell是一个命令行形式的Shell,是一个脚本运行的环境。在Windows Vista及以后的操作系统中自带。它的功能比CMD要强大得多。操作系统可以粗略分成kernel和Shell两部分,其中,Shell是操作系统与外部的主要接口,在操作系统的外层,为用户提供与操作系统kernel沟通的途径。Shell一定非得是这种“黑框框”,Windows的图形界面也是。Windows PowerShell是在 .NET Framework公共语言运行时 (CLR) 和 .NET Framework 的基础上构建的,而原来的cmd并不能利用.NET Framework。关于.NET Framework可以参考【入门科普向】带你走近.NET Framework。
如图,不要认为PowerShell只能输入一行一行简单的命令,它有很多高级语法,有强大的表达能力,结合.NET Framework,能做很多事情,下面是一个贪吃蛇游戏,代码位于https://github.com/spitfire05/PowerSnake(不是我写的)
③系统内置的管理员账户
在Windows操作系统中,系统内置的管理员账户有较大的修改操作系统的权限(但不是全部)。在Windows 7中,系统内置的Administrator账户默认不是开启的,在安装Windows 7时(指用官方系统镜像)时登陆的账户不是系统自带的Administrators组的“超级管理员账户”。当程序需要管理员权限时,需要用户确认提权,才能继续执行(默认设置下)。在Windows8及以上的系统中,直接使用系统内置的管理员账户不能打开Metro应用。不要使用到注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System中将FilterAdministratorToken改成“1”
之类的方法,相当于“提权后降权”。
*有特殊需要必须临时使用这个账户的,可在命令提示符(管理员)下输入“net user administrator /active:yes”,注销当前帐户后切换到该账户,之后再用“net user administrator /active:no”关闭。
不建议使用这个内置账户,在Windows XP中,管理员就是完整的管理员权限,否则就是是受限账户。当然你可以保留XP时代的习惯,必须付出一定代价。后文会简单介绍UAC(用户账户控制)。
Ⅱ 注册表/UAC
①注册表基础知识
注册表一个全局的系统配置数据库。它有着和磁盘上的文件类似的组织方式(树形)。
磁盘上的“文件夹”和注册表中的“键”逻辑上是一样的,这种对应关系还有“文件”->“键值”。键值必须在键下面,不能单独存在。键下面还可以含子键,一个键里面可以有很多个键值。如图:
Environment是一个键,它有自己的子键(图片右方),它的父键是S-1-5-18,再上一层就是“根键”HKEY_USERS。图中的“计算机”不是键。
在Windows XP后有六个根键(有一个在注册表编辑器下不可见)。根键不能手动添加,也不能强制删除,所有的键的最顶层的根一定是六大根键中的一个。根键下面的子键没有被限制不能删除,但可能导致错误。
六大根键:
HKEY_CLASSES_ROOT
HKEY_CURRENT_CONFIG
HKEY_LOCAL_MACHINE
HKEY_CURRENT_USER
HKEY_USERS
HKEY_PERFORMANCE_DATA(注册表编辑器中不可见)
*可以通过查看性能监视器来查看HKEY_PERFORMANCE_DATA中的数据。HKEY_PERFORMANCE_DATA里的数据有特殊格式
“键值”是有数据类型的,数据类型的意思是:假设一个人的名字为Tom,这项数据的类型就应该是“字符串”而不是“数字”,年龄5岁,年龄这项数据的类型是“整数”,所以不同类型的数据就有不同操作方式,比如整数可以相乘但名字就不能。在注册表中的数据类型要稍微复杂些。输入的数据必须符合相关类型。
常见的数据类型有:
REG_SZ:固定长度的Unicode字符串
REG_EXPAND:可变长的Unicode字符串
REG_DWORD:32位整数
REG_QWORD:64位整数,有效范围大于REG_DWORD
REG_MULTI:Unicode字符串数组(以NULL结尾)
*Unicode是一个标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
*不同数据类型有不同的编辑方式,另外,字符串类型的数据可以是纯数字,这些数字被视为字符串而不是数字,但反过来不能
**数据的进制不影响最终结果
HKEY_CLASSES_ROOT存储有关文件关联和COM(本帖不介绍,组建对象模型)的注册信息。
HKEY_CURRENT_CONFIG是一个“链接”,它实际指向的是HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current,有些旧的程序依赖这个键,所以没去掉。
HKEY_LOCAL_MACHINE存储有关系统的信息。HKEY_CURRENT_USER和HKEY_USERS,前一个存储当前登录用户的相关数据,后一个是所有账户的信息。HKEY_CURRENT_USER指向HKEY_USERS下当前用户专有的子键。
没有HKEY_DYN_DATA (即插即用和系统性能的动态信息)这一个根键,它是Windows98中的。
一般来说,一个设计良好的软件是不需要修改注册表来改变其配置的,会有相应的用户界面来操作这些数据,无需知道数据在哪里存放,修改不能通过软件界面修改的参数可能有一些意想不到的效果,比如360杀毒可以修改相关注册表项来改变“已保护的天数”,但有些情况下会是软件错误。如果确实需要修改某一项系统注册表项,建议到MSDN去查询该项的具体含义。
注册表保存在哪里?是不是一个巨大的数据库文件?
注册表是逻辑上的一个数据整体,但它并不是对应着一个大文件。它可能是在磁盘上的静态的数据,也可以是Windows内核负责管理的内存中的某些数据。在磁盘上,有一系列的被称为“hive”的文件(它们是独立的)。每一个hive下面包含一个注册表树,一定有一个键作为这个数的根结点(或者起点),这个键下有子键。不是注册表编辑器中看到的根键一定对应一个本地磁盘上的hive。况且某些hive没有关联文件,比如HKEY_LOCAL_MACHINE\HARDWARE hive,它主要含有设备及设备相关的资源信息,系统在引导的时候会进行这些工作。像这种hive被称为“易失的”(volatile)。
①操作注册表
可以通过注册表编辑器来访问注册表,也可以使用CMD&PowerShell来访问,另外,Windows提供了一组访问注册表的API函数,如:
[mw_shl_code=css,true]LONG RegCreateKeyEx(
HKEY hKey,
LPCTSTR lpSubKey,
DWORD Reserved,
LPTSTR lpClass,
DWORD dwOptions,
REGSAM samDesired,
LPSECURITY_ATTRIBUTESlpSecurityAttributes,
PHKEY phkResult,
LPDWORD lpdwDisposition
);//创建键
LONG RegOpenKeyEx(
HKEY hkey,
LPCTSTR lpSubKey,
DWORD ulOption,
REGSAM samDesired,
PHKEY phkResult
);//打开键
LONG RegQueryValueEx(
HKEY hKey,
LPTSTR lpValueName,
LPDWORD lpReserved,
LPDWORD lpType,
LPBYTE lpData,
LPDWORD lpcbData
);//读取数据
LONG RegSetValueEx(
HKEY hKey,
LPCTSTR lpValueName,
DWORD Reserved,
DWORD dwType,
CONST BYTE *lpData,
DWORD cbData
);//设置键
LONG RegDeleteKey(
HKEY hKey,
LPCTSTR lpSubKEY
);//删除键[/mw_shl_code]
.reg文件是“注册表脚本”,双击后会将相关的数据导入注册表,注册表编辑器支持导出.reg文件。
可以使用CMD来操作注册表,语法略有不同。另外也可以用PowerShell,如果利用.NET类库更为方便,只需要生成相关对象调用方法即可,完全不需要直接调用上述繁杂的API函数。(参见前文的“走进.NET Framework”)
③UAC简介
UAC(User Account Control,用户帐户控制)是微软为提高系统安全而在Windows Vista中引入的新技术,它要求用户在执行可能会影响计算机运行的操作或执行更改影响其他用户的设置的操作之前,提供权限或管理员密码。通过在这些操作启动前对其进行验证,UAC 可以帮助防止恶意软件和间谍软件在未经许可的情况下在计算机上进行安装或对计算机进行更改。
很多用户在安装完新系统会完全关闭UAC,开启系统内置的管理员,关闭SmartScreen,禁用Windows Update,说不定再关闭掉防火墙。更有可能,安装的系统是被恶意修改过的版本,本来就关闭了这些功能,或者使其损坏手动也无法开启。如下图:
其实花大力气设计的UAC的目的主要是让用户能够以标准的账户权限运行。没有管理员权限,用户就(一般)不能故意修改一些重要的数据,当然恶意软件也(一般)会受到很大的限制,尤其是那种“安装器”(一点击就冒出一大堆垃圾软件)。
在用户只运行与标准账户权限相符合的软件时,也可能有一些操作必须要管理员权限。例如在安装MSE时,需要修改注册表,而修改注册表必须是管理权限,就会需要“提权”。这个机制允许用户以管理员权限启动某个进程。如果这个程序是有Microsoft签名的程序,而且在当前运行的Windows的系统目录中,就会出现如下弹框:
如果这个程序有有效的非Microsoft的签名,或者有Microsoft的签名但路径不在系统目录中,会有以下弹框,注意看左上角的不同,这次是一个带问号的盾牌:
如果这几个都没有,那就是以下的弹框,橙色以示警告:
甚至是这种红色弹窗,这种情况下非确定可信就别打开了,单击“否”不会执行程序,也不会造成危害:
UAC不是不可绕过的,但关闭了UAC使风险变得更大,尤其在内置管理员账户下,不需要Bypass UAC就可以像XP那样修改关键数据。
可能有些童鞋听过“文件虚拟化”以及“注册表虚拟化”之类的概念。需要说明的是:UAC不是什么虚拟机,文件虚拟化和注册表虚拟化和硬件虚拟化是两回事。认为文件虚拟化和注册表虚拟化是将程序放入虚拟机中运行从而提高安全性是错误的。
虚拟化的引入是为了改进 Windows Vista上以标准用户运行的程序的兼容性,它是一项为兼容性而设计的技术。在 Windows Vista之前,大部分应用程序是以管理员权限运行的。所以,应用程序可以自由地读写系统文件和注册表键(前提是不被安全软件拦截)。如果标准用户运行这些程序,会由于权限不够而错误。Windows Vista通过重定向对用户配置文件中每个用户的写操作来改进标准用户程序的兼容性。
[quote]例如,如果一个程序试图写入到 C:\Program Files\MySoftware\setup.ini,但用户没有写入那个目录的权限,这个写操作就会被重定向至 C:\Users\Username\AppData\Local\VirtualStore\Program Files\MySoftware\setup.ini。对于注册表,如果一个程序试图写入到 HKEY_LOCAL_MACHINE\Software\MySoftware\,它会被自动重定向到 HKEY_CURRENT_USER\Software\Classes\VirtualStore\MACHINE\Software\Mysoftware(或者HKEY_USERS\UserSID_Classes\VirtualStore\Machine\Software\MySoftWare)。
下图来自MSDN,说明了这一虚拟化(重定向)的过程:
应用程序如果有管理员权限可以写入位置存储文件,这种情况下以标准用户模式运行的程序可能会由于权限不足导致程序执行失败。如果某个目录仅有管理员可写,Windows会将文件操作全部重定向到VirtualStore中的相关目录中去。
当程序再次读取这个文件时,会使用 VirtualStore 目录里的那一个文件。Windows单方面完成这个“虚拟化”,而程序“认为”它自己成功地直接读写了Program Files 目录,但实际上并不是,它在访问虚拟化的资源。下图同样来自MSDN:
注册表虚拟化,过程类似于文件虚拟化。
如果一个新编写的程序也得“虚拟化”,可以认为它在这一方面是不合格的,这项技术是为了尽可能的保证旧程序在新版本Windows中执行时不出错,不建议“依赖”操作系统的这种机制。虚拟化在某些情况下是不启用的,比如带有 requestedExecutionLevel(请求执行等级)的可执行程序,64 位程序(因为假定64位程序都是“新程序”),内核模式调用者(你是不是看出了什么?)等。
*requestedExecutionLevel,小盾牌图标
传统的沙箱技术或者硬件虚拟化和这个虚拟化做对比,可以简单的认为这种虚拟化是一种系统单方面的“重定向”,主要目的是让老程序运行。
用户账户控制级别有四级(安全桌面是指“桌面变暗,只看得见弹出一个UAC提权窗口:)
•始终通知:最高级别状态,在任何情况下对操作系统进行更改等情况,都会弹出提示窗口(并启用安全桌面),请求用户确认。这个级别的设置提示最多。
•默认级别:这个级别下,应用程序试图改变计算机设置时会提示用户,同时,在该模式下将启用安全桌面,以防绕过UAC更改系统设置。默认级别可以有效防范恶意程序在用户不知情的情况下修改系统设置。
•第三级:不开启安全桌面。
•第四级:不推荐使用。一般情况下,保持系统默认级别即可。
④“Principle of Least Privilege”
低权限完成的任务就不应向操作系统申请高权限。正常的应用程序的大部分功能一般都能通过正常API函数完成。“最小权限”即只申请需要的权限完成任务就行。最小特权原则要求每个用户和程序在操作时应当使用尽可能少的特权。为什么这样?因为可减少或者避免由于错误或是侵入者伪装合法的对象而用高特权破坏系统的稳定性。它还能减少程序间互相冲突的机会。
不过对于某些程序员来说,这样显得麻烦了许多,还不如直接申请管理员权限方便(或者加内核模式驱动,权限更高),使用这样的软件是有一定风险的,不必要的特权就算不被恶意入侵者利用,也有可能被程序无意或者有意的不当使用。
Ⅲ 动态链接库
① 动态链接库的意义
这里不讲述COM(“组建对象模型”)。
先不说那些概念,我们先看一个小事例。
在本贴前面,提到了.NET Framework和CLR,但假定读者并不清楚这是什么,又为了理解内容,怎么办?有两种选择:第一种,把文章链接插入到正文部分,第二种,将关于.NET Framework帖子的所有内容全部复制进来,不用切换到另外的帖子阅读。第一种显然要简单些,而第二种,会让文章变得很长,阅读体验不佳。但是有一个问题,若是那篇帖子无法打开呢,只留一个链接没有任何用处。而将帖子内容全部复制就没有这个问题。
静态链接库,是一个二进制的文件(在Windows系统上通常是.lib)。当在编译程序形成可执行文件时,链接器会负责把静态链接库中有关的数据复制出来,然后“拼凑出”一个新程序来。当程序在其它计算机上运行时,不需要要求有这个.lib文件,这个就相当于上一段所说的把“帖子内容全部复制过来了”。动态链接库文件的后缀名不一定是.dll,也可能是.ocx,甚至是.exe等,没有强制要求。
*MFC,使用静态链接库,生成的程序文件体积相对较大
如果要用动态链接库,可能会需要两个文件,一个称为“引入库”,另一个是相关的DLL文件。不用关心什么是引入库,DLL文件中的代码不会嵌入到可执行程序中去,程序中只保存了相关的符号,在运行的时候,再去动态加载DLL文件。程序会从(大概如此,可以人为改变。总之不会只在程序所在目录里搜索。):
1.程序所在目录。
2.加载 DLL 时所在的当前目录。
3.系统目录即 SYSTEM32 目录。
4.SYSTEM 目录。
5.Windows目录。
6.PATH环境变量中列出的目录
寻找这个DLL,如果没有,就报错。
在Windows系统中使用DLL有很多优点,多个应用程序可以共享一个DLL文件(所谓的“资源共享"),缩小了应用程序的执行代码;是DLL文件作为一个单独的程序模块,是相对独立的,如果在软件需要升级的时候,开发人员只需要修改相应的DLL文件,而且,当DLL中的函数改变后,函数的签名不变,主程序代码并不需要重新编译。(在这一方面Microsoft做得很好,Win32 API在系统升级换代的时候调用方面没有改变)。这样能提高了软件开发和维护的效率。
如图,利用Visual Basic 6.0生成的ActiveX DLL文件(一种DLL文件的类型),可以由C++调用 。调用不同语言之间按照某种规范(此处省略)生成的DLL文件时,不需要考虑实现语言是什么,只需使用其中的功能,把它当作一个黑盒来看待。
当程序运行时,每个进程有自己私有的地址空间,不同进程之间是隔离的。如果A进程在运行时需要加载一份01.dll,那么这个DLL就可以被A进程访问了,加载到A进程的地址空间中,如果这时B进程也需要这个DLL,并不会重复加载,而是将A进程中01.dll映射到B进程的地址空间中,实际上A,B共用这一个DLL,这时再有C进程也是一样的。
②几个重要的系统DLL文件
kernel32.dll,Advapi.dll,User32.dll,Gdi32.dll:它们是Windows核心子系统的DLL(函数的实现不在这里,它们只是提供一个访问接口)
Kernel32.dll与控制系统的内存管理,数据输入输出以及中断处理有关。在Windows启动的同时,就会驻留加载到内存中。
Advapi.dll是一个高级API应用程序接口服务库的一部分,包含的函数与对象安全,注册表的操控以及事件日志有关。
User32.dll是Windows用户界面相关应用程序接口,用于包括Windows处理,基本用户界面等特性,如创建窗口和发送消息。
Gdi32.dll是Windows GDI图形用户界面相关的,包含的函数用来绘制图像和显示文字。
Ntdll.dll是Windows从Ring3到Ring0的入口,它是一个特殊的系统支持库,它有两类函数:
系统服务分发存根,会指向Windows执行体的服务。
内部支持函数,供子系统DLL等使用。
Win32 API调用最后都转移到了Ntdll.dll,而Ntdll.dll又将其转移到了Ntoskrnl.exe。Ntdll.dll是一个操作系统组件,它为Native API准确地提供服务,Ntdll.dll是Native API(可看作是内核服务)在用户模式下的“前台”。
③DLL注入是什么
根据上一篇帖子的介绍,每个进程之间是相对独立的,要想互相通信就得调用Win32 API。一个不能直接访问到另一个进程的地址空间,所以注入得借助一些手段。DLL注入有什么用?
1、操纵的数据不在当前进程内的数据。
2、对目标进程中的函数进行拦截。
3、你想编写一些函数用于增强或增加目标进程功能,意思就是给它附加一点有用的东西。
4、搞破坏,即使你将恶意程序的进程结束掉也不行,恶意代码已经插入到某些进程中去了。
进程内的线程总是能够访问进程内的数据,所以可以有一种思路,假如A进程想让B进程做点事,就得在进程中加载一个线程(算是一个“内鬼”,服务于A进程,线程的函数在DLL里)。下面描述一个简化后的的DLL注入的过程(伪代码,这里就不贴注入的代码了,没有API编程的经验是很难理解里面的代码的):(DLL的注入方法很多,有些较复杂,这里只说最简单的)
- Inject(ProcessID pid)
- LoadLibraryToOtherProcess(
- ProcessID=pid,
- DLLPath="Recovery.dll"); //让其他进程加载DLL,要执行的代码在Recovery.dll中
- Thread_Handle hThread =CreateRemoteThread(
- ProcessID=pid,
- DLL=CURRENT);//创建远程线程
- return hThread;
复制代码
然后这个远程线程就可以干注入者进程想干的事了,比如可以修改数据,终止目标线程内的其他线程,终止目标进程,执行恶意行为,让别人“误以为”是目标进程做的事。注入有很多的方法,在有安全软件的情况下并不容易。
本帖到此结束,仅供参考。
|