这是作者网络安全自学教程系列,主要是关于安全工具和实践操作的在线笔记,特分享出来与博友们学习,希望您喜欢,一起进步。这篇文章将带着大家来学习《Windows黑客编程技术详解》,其作者是甘迪文老师,推荐大家购买来学习。作者将采用实际编程和图文结合的方式进行分享,并且会进一步补充知识点。第四篇文章主要介绍木马病毒自启动技术,包括注册表、快速启动目录、计划任务和系统服务,希望对您有所帮助。
对于一个病毒木马来说,重要的不仅是如何进行破坏,还有如何执行。同样,如何开始也非常重要,病毒木马只有加载到内存中开始运行,才能真正体现出它的破坏力。否则,它只是一个普通的磁盘文件,对于计算机用户的数据、隐私构不成任何威胁。
即使成功植入模块并启动攻击模块,依然不能解决永生驻留的问题(持久性攻击)。解决永生驻留的第一步便是如何实现伴随系统启动而启动的问题,即开机自启动。这样,即使用户关机重启,病毒木马也随着系统的启动,而由系统加载到内存中运行,从而窃取用户数据和隐私。因此,开机自启动技术是病毒木马至关重要的技术,也是杀软重点监测的技术。对于杀软来说,只要把守住自启动的入口,就可以把病毒木马扼杀在摇篮中。
作者的github资源:
软件安全:https://github.com/eastmountyxz/Software-Security-Course
其他工具:https://github.com/eastmountyxz/NetworkSecuritySelf-study
Windows-Hacker:https://github.com/eastmountyxz/Windows-Hacker-Exp
声明:本人坚决反对利用教学方法进行犯罪的行为,一切犯罪行为必将受到严惩,绿色网络需要我们共同维护,更推荐大家了解它们背后的原理,更好地进行防护。
前文学习:
[网络安全自学篇] 一.入门笔记之看雪Web安全学习及异或解密示例
[网络安全自学篇] 二.Chrome浏览器保留密码功能渗透解析及登录加密入门笔记
[网络安全自学篇] 三.Burp Suite工具安装配置、Proxy基础用法及暴库示例
[网络安全自学篇] 四.实验吧CTF实战之WEB渗透和隐写术解密
[网络安全自学篇] 五.IDA Pro反汇编工具初识及逆向工程解密实战
[网络安全自学篇] 六.OllyDbg动态分析工具基础用法及Crakeme逆向
[网络安全自学篇] 七.快手视频下载之Chrome浏览器Network分析及Python爬虫探讨
[网络安全自学篇] 八.Web漏洞及端口扫描之Nmap、ThreatScan和DirBuster工具
[网络安全自学篇] 九.社会工程学之基础概念、IP获取、IP物理定位、文件属性
[网络安全自学篇] 十.论文之基于机器学习算法的主机恶意代码
[网络安全自学篇] 十一.虚拟机VMware+Kali安装入门及Sqlmap基本用法
[网络安全自学篇] 十二.Wireshark安装入门及抓取网站用户名密码(一)
[网络安全自学篇] 十三.Wireshark抓包原理(ARP劫持、MAC泛洪)及数据流追踪和图像抓取(二)
[网络安全自学篇] 十四.Python攻防之基础常识、正则表达式、Web编程和套接字通信(一)
[网络安全自学篇] 十五.Python攻防之多线程、C段扫描和数据库编程(二)
[网络安全自学篇] 十六.Python攻防之弱口令、自定义字典生成及网站暴库防护
[网络安全自学篇] 十七.Python攻防之构建Web目录扫描器及ip代理池(四)
[网络安全自学篇] 十八.XSS跨站脚本攻击原理及代码攻防演示(一)
[网络安全自学篇] 十九.Powershell基础入门及常见用法(一)
[网络安全自学篇] 二十.Powershell基础入门及常见用法(二)
[网络安全自学篇] 二十一.GeekPwn极客大赛之安全攻防技术总结及ShowTime
[网络安全自学篇] 二十二.Web渗透之网站信息、域名信息、端口信息、敏感信息及指纹信息收集
[网络安全自学篇] 二十三.基于机器学习的恶意请求识别及安全领域中的机器学习
[网络安全自学篇] 二十四.基于机器学习的恶意代码识别及人工智能中的恶意代码检测
[网络安全自学篇] 二十五.Web安全学习路线及木马、病毒和防御初探
[网络安全自学篇] 二十六.Shodan搜索引擎详解及Python命令行调用
[网络安全自学篇] 二十七.Sqlmap基础用法、CTF实战及请求参数设置(一)
[网络安全自学篇] 二十八.文件上传漏洞和Caidao入门及防御原理(一)
[网络安全自学篇] 二十九.文件上传漏洞和IIS6.0解析漏洞及防御原理(二)
[网络安全自学篇] 三十.文件上传漏洞、编辑器漏洞和IIS高版本漏洞及防御(三)
[网络安全自学篇] 三十一.文件上传漏洞之Upload-labs靶场及CTF题目01-10(四)
[网络安全自学篇] 三十二.文件上传漏洞之Upload-labs靶场及CTF题目11-20(五)
[网络安全自学篇] 三十三.文件上传漏洞之绕狗一句话原理和绕过安全狗(六)
[网络安全自学篇] 三十四.Windows系统漏洞之5次Shift漏洞启动计算机
[网络安全自学篇] 三十五.恶意代码攻击溯源及恶意样本分析
[网络安全自学篇] 三十六.WinRAR漏洞复现(CVE-2018-20250)及恶意软件自启动劫持
[网络安全自学篇] 三十七.Web渗透提高班之hack the box在线靶场注册及入门知识(一)
[网络安全自学篇] 三十八.hack the box渗透之BurpSuite和Hydra密码爆破及Python加密Post请求(二)
[网络安全自学篇] 三十九.hack the box渗透之DirBuster扫描路径及Sqlmap高级注入用法(三)
[网络安全自学篇] 四十.phpMyAdmin 4.8.1后台文件包含漏洞复现及详解(CVE-2018-12613)
[网络安全自学篇] 四十一.中间人攻击和ARP欺骗原理详解及漏洞还原
[网络安全自学篇] 四十二.DNS欺骗和钓鱼网站原理详解及漏洞还原
[网络安全自学篇] 四十三.木马原理详解、远程服务器IPC$漏洞及木马植入实验
[网络安全自学篇] 四十四.Windows远程桌面服务漏洞(CVE-2019-0708)复现及详解
[网络安全自学篇] 四十五.病毒详解及批处理病毒制作(自启动、修改密码、定时关机、蓝屏、进程关闭)
[网络安全自学篇] 四十六.微软证书漏洞CVE-2020-0601 (上)Windows验证机制及可执行文件签名复现
[网络安全自学篇] 四十七.微软证书漏洞CVE-2020-0601 (下)Windows证书签名及HTTPS网站劫持
[网络安全自学篇] 四十八.Cracer第八期——(1)安全术语、Web渗透流程、Windows基础、注册表及黑客常用DOS命令
[网络安全自学篇] 四十九.Procmon软件基本用法及文件进程、注册表查看
[网络安全自学篇] 五十.虚拟机基础之安装XP系统、文件共享、网络快照设置及Wireshark抓取BBS密码
[网络安全自学篇] 五十一.恶意样本分析及HGZ木马控制目标服务器
[网络安全自学篇] 五十二.Windows漏洞利用之栈溢出原理和栈保护GS机制
[网络安全自学篇] 五十三.Windows漏洞利用之Metasploit实现栈溢出攻击及反弹shell
[网络安全自学篇] 五十四.Windows漏洞利用之基于SEH异常处理机制的栈溢出攻击及shell提取
[网络安全自学篇] 五十五.Windows漏洞利用之构建ROP链绕过DEP并获取Shell
[网络安全自学篇] 五十六.i春秋老师分享小白渗透之路及Web渗透技术总结
[网络安全自学篇] 五十七.PE文件逆向之什么是数字签名及Signtool签名工具详解(一)
[网络安全自学篇] 五十八.Windows漏洞利用之再看CVE-2019-0708及Metasploit反弹shell
[网络安全自学篇] 五十九.Windows漏洞利用之MS08-067远程代码执行漏洞复现及shell深度提权
[网络安全自学篇] 六十.Cracer第八期——(2)五万字总结Linux基础知识和常用渗透命令
[网络安全自学篇] 六十一.PE文件逆向之数字签名详细解析及Signcode、PEView、010Editor、Asn1View等工具用法(二)
[网络安全自学篇] 六十二.PE文件逆向之PE文件解析、PE编辑工具使用和PE结构修改(三)
[网络安全自学篇] 六十三.hack the box渗透之OpenAdmin题目及蚁剑管理员提权(四)
[网络安全自学篇] 六十四.Windows漏洞利用之SMBv3服务远程代码执行漏洞(CVE-2020-0796)复现及详解
[网络安全自学篇] 六十五.Vulnhub靶机渗透之环境搭建及JIS-CTF入门和蚁剑提权示例(一)
[网络安全自学篇] 六十六.Vulnhub靶机渗透之DC-1提权和Drupal漏洞利用(二)
[网络安全自学篇] 六十七.WannaCry勒索病毒复现及分析(一)Python利用永恒之蓝及Win7勒索加密
[网络安全自学篇] 六十八.WannaCry勒索病毒复现及分析(二)MS17-010利用及病毒解析
[网络安全自学篇] 六十九.宏病毒之入门基础、防御措施、自发邮件及APT28样本分析
[网络安全自学篇] 七十.WannaCry勒索病毒复现及分析(三)蠕虫传播机制分析及IDA和OD逆向
[网络安全自学篇] 七十一.深信服分享之外部威胁防护和勒索病毒对抗
[网络安全自学篇] 七十二.逆向分析之OllyDbg动态调试工具(一)基础入门及TraceMe案例分析
[网络安全自学篇] 七十三.WannaCry勒索病毒复现及分析(四)蠕虫传播机制全网源码详细解读
[网络安全自学篇] 七十四.APT攻击检测溯源与常见APT组织的攻击案例
[网络安全自学篇] 七十五.Vulnhub靶机渗透之bulldog信息收集和nc反弹shell(三)
[网络安全自学篇] 七十六.逆向分析之OllyDbg动态调试工具(二)INT3断点、反调试、硬件断点与内存断点
[网络安全自学篇] 七十七.恶意代码与APT攻击中的武器(强推Seak老师)
[网络安全自学篇] 七十八.XSS跨站脚本攻击案例分享及总结(二)
[网络安全自学篇] 七十九.Windows PE病毒原理、分类及感染方式详解
[网络安全自学篇] 八十.WHUCTF之WEB类解题思路WP(代码审计、文件包含、过滤绕过、SQL注入)
[网络安全自学篇] 八十一.WHUCTF之WEB类解题思路WP(文件上传漏洞、冰蝎蚁剑、反序列化phar)
[网络安全自学篇] 八十二.WHUCTF之隐写和逆向类解题思路WP(文字解密、图片解密、佛语解码、冰蝎流量分析、逆向分析)
[网络安全自学篇] 八十三.WHUCTF之CSS注入、越权、csrf-token窃取及XSS总结
[网络安全自学篇] 八十四.《Windows黑客编程技术详解》之VS环境配置、基础知识及DLL延迟加载详解
[网络安全自学篇] 八十五.《Windows黑客编程技术详解》之注入技术详解(全局钩子、远线程钩子、突破Session 0注入、APC注入)
[网络安全自学篇] 八十六.威胁情报分析之Python抓取FreeBuf网站APT文章(上)
[网络安全自学篇] 八十七.恶意代码检测技术详解及总结
[网络安全自学篇] 八十八.基于机器学习的恶意代码检测技术详解
[网络安全自学篇] 八十九.PE文件解析之通过Python获取时间戳判断软件来源地区
[网络安全自学篇] 九十.远控木马详解及APT攻击中的远控
[网络安全自学篇] 九十一.阿里云搭建LNMP环境及实现PHP自定义网站IP访问 (1)
[网络安全自学篇] 九十二.《Windows黑客编程技术详解》之病毒启动技术创建进程API、突破SESSION0隔离、内存加载详解(3)
前文欣赏:
[渗透&攻防] 一.从数据库原理学习网络攻防及防止SQL注入
[渗透&攻防] 二.SQL MAP工具从零解读数据库及基础用法
[渗透&攻防] 三.数据库之差异备份及Caidao利器
[渗透&攻防] 四.详解MySQL数据库攻防及Fiddler神器分析数据包
为方便用户使用,无论是恶意程序或正常应用软件,都不用人为地去运行程序,程序会提供开机自启动功能,伴随着系统启动而自己运行起来。由于开机自启动功能的特殊性,它一直都是杀软和病毒木马重点博弈的地方。
实现开机自启动的途径和方式有很多种,其中修改注册表方式应用最为广泛,注册表相当于操作系统的数据库,记录着系统中方方面面的数据,其中不乏直接或间接导致开机自启动的数据。本部分介绍向Run注册表中添加程序路径的方式,以实现开机自启动。
该部分参考作者2014年分享的文章,当时是通过注册表获取电脑U盘历史记录。
注册表(Registry)是Windows系统中一个重要的数据库,它用于存储有关应用程序、用户和系统信息。注册表的结构就像一颗树.树的顶级节点(hive)不能添加、修改和删除,如下图所示是Windows注册表的顶级节点:
在C#中对注册表进行操作,需要引用命名空间using Microsoft.Win32。
注册表中常用的数据类型有:
(1) RegOpenKeyEx函数
打开一个指定的注册表键。
LONG WINAPI RegOpenKeyEx(
HKEY hKey, //需要打开的主键的名称
LPCTSTR lpSubKey, //需要打开的子键的名称
DWORD ulOptions, //保留,设为0
REGSAM samDesired, //安全访问标记,也就是权限
PHKEY phkResult //得到的将要打开键的句柄
)
参数:
标记 | 含义 |
---|---|
KEY_CREATE_LINK | 准许生成符号键 |
KEY_CREATE_SUB_KEY | 准许生成子键 |
KEY_ENUMERATE_SUB_KEYS | 准许生成枚举子键 |
KEY_EXECUTE | 准许进行读操作 |
KEY_NOTIFY | 准许更换通告 |
KEY_QUERY_VALUE | 准许查询子键 |
KEY_SET_VALUE | 准许设置子键的数值 |
KEY_ALL_ACCESS | 提供完全访问 |
KEY_READ | 是KEY_QUERY_VALUE、KEY_ENUMERATE_SUB_KEYS、KEY_NOTIFY的组合 |
KEY_WRITE | 是KEY_SET_VALUE和KEY_CREATE_SUB_KEY组合行 |
KEY_WOW64_64KEY | 表示64位Windows系统的应用程序在64位注册表视图上运行 |
KEY_WOW64_32KEY | 表示64位Windows系统的应用程序在32位注册表视图上运行 |
返回值:
如果函数调用成功,则返回零(ERROR_SUCCESS),否则返回值为内文件WINERROR.h定义的一个非零的错误代码。注意,不像RegCreateKeyEx 函数,当指定键不存在RegOpenKeyEx函数不创建新键。
(2) RegSetValueEx函数
在注册表项下设置指定值的数据和类型。返回零表示成功,返回其他任何值都代表一个错误代码。
LONG RegSetValueEx(
HKEY hKey, //指定一个已打开句柄或标准项名
LPCTSTR lpValueName, //指向一个字符串的指针,该字符串包含设置值的名称
DWORD Reserved, //保留值,必须强制为0
DWORD dwType, //指定存储的数据类型,如REG_BINARY\REG_DWORD
CONST BYTE *lpData, //指向一个缓冲区,包含指定名称存储的数据
DWORD cbData //指定由lpData参数指向的数据大小
);
参考百度百科:
理解修改注册表实现开机自启动功能的一个重要前提是,Windows提供了专门的开机自启动注册表。在每次开机完成后,它都会在这个注册表键下遍历键值,以获取键值中的程序路径,并创建进程启动程序。所以,要想修改注册表实现开机自启动,只需要在这个注册表键下添加我们想要设置自启动程序的路径即可。
常见路径为如下,主要是主键不同,二者功能相似,都可以实现开机自启动。推荐读者阅读下作者最近分享的 “熊猫烧香病毒” 启动文章。
通过在上述两个注册表键中设置一个新的键值,写入自启动程序的路径。注意,修改主表的权限问题,编程时要修改HKEY_LOCAL_MACHINE主键的注册表,这要有管理员权限;而修改HKEY_CURRENT_USER主键的注册表只需要用户默认权限就可以实现。
如果程序运行在64位Windows系统上,则需要注意注册表重定位的问题。在64位Windows系统中,为了兼容32位程序的正常执行,64位的Windows系统采用重定向机制。系统为关键的文件夹和关键注册表创建两个副本,使得32位程序在64位系统上不仅能够操作关键文件夹和关键注册表,还可以避免与64为程序冲突。
第一步,新建C++空项目,项目名称为“Ziqidong01”。
第二步,新建源文件“main.cpp”。
第三步,编写如下代码,实现注册表开机自启动。
#include
#include
#include
int main(int argc, char* argv[])
{
//设置子键
LPCTSTR lpSubKey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
//默认权限
HKEY hKey;
//设置键值
REGSAM flag = KEY_WOW64_64KEY;
DWORD dwDisposition = REG_OPENED_EXISTING_KEY;
//打开注册表键
LONG Ret = ::RegOpenKeyEx(HKEY_CURRENT_USER, lpSubKey, 0, KEY_WRITE, &hKey);
//错误代码5拒绝访问-管理员权限
//LONG Ret = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpSubKey, 0, KEY_ALL_ACCESS | flag, &hKey);
if (ERROR_SUCCESS != Ret)
{
printf("注册表打开失败\n");
printf("%d", Ret);
return 0;
}
//修改注册表键值 实现开机自启动桌面计算器
TCHAR* pchrName = "C:\\Users\\xiuzhang\\Desktop\\calc.exe";
LONG SetRet = ::RegSetValueEx(hKey,
TEXT("Eastmount"),
NULL,
REG_SZ,
(LPBYTE)pchrName,
strlen(pchrName) * sizeof(TCHAR) + 1);
if (ERROR_SUCCESS != SetRet)
{
//关闭注册表键值
::RegCloseKey(hKey);
printf("注册表写入失败\n");
return 0;
}
//关闭注册表键值
::RegCloseKey(hKey);
system("pause");
return 0;
}
第四步,运行代码前我们先看看注册表的情况。
360、钉钉、迅雷、有道这几款软件就存在自启动,如果是木马或病毒,我们也能够发现。
第五步,运行程序再检查注册表,发现“Eastmount”键值被添加。
需要注意让360安全软件允许此次操作。
此时结果如下图所示,“Eastmount”成功写入,并对应桌面自启动计算器(calc.exe)。
我们尝试重新启动电脑,开机发现成功打开了计算器,哈哈,成功!因为执行两种写入,故开机打开了两个计算器程序。
第六步,尝试另一种方式打开注册表,如果失败返回代码5(拒绝访问),需要以管理员权限打开该程序。
注意,HKEY_LOCAL_MACHINE主键的注册表要有管理员权限,并且64位系统中关键的注册表被重定位了,重定位路径如下。在上面的代码中,我们直接打开RegOpenKeyEx函数设置KEY_WOW64_64KEY访问标志,从而避免重定位的影响,直接访问指定的注册表路径。
同时,编程过程中会遇到各种错误,请大家一定实际去编写代码,学会谷歌百度独立解决。 比如"const char *"类型的实参与“LPCWSTR”类型的形参不兼容,这是由于字符编码问题引起的,VC6 默认使用的 MBCS(多字节字符集) 编码,而VS2010及高版本VS默认使用Unicode编码。基本的解决方法如下:
注意:当我们在编写反病毒或安全软件时,直接枚举开机自启动注册表中的键值,可以获取开机启动项的信息。
在Windows系统中,存在很多可以实现开机自启动的地方。大家可以使用前面介绍过的Procmon.exe工具来监控系统开机自启动的过程,从而找到可利用的切入点。接着我们介绍一种不用修改任何系统数据,并实现较为简单的开机自启动方法——利用快速启动目录来实现。
在APT攻击中,通常会利用WinRAR漏洞(CVE-2018-20250)实现自启动。当恶意ACE文件被受害者解压之后,会释放恶意木马至指定目录(系统自启动文件夹),受害者重启电脑会执行恶意木马。如下图所示,它们其实就是利用了这种方法。
SHGetSpecialFolderPath函数
获取指定的系统路径。
BOOL SHGetSpecialFolderPath(
HWND hwndOwner,
LPTSTR lpszPath,
int nFolder,
BOOL fCreate
);
参数:
返回值:
如果返回TRUE表示执行成功;否则执行失败。
Windows系统有自带的快速启动文件夹,它是最简单的自启动方式。只要把程序放入到这个快速启动文件夹中,系统在启动时就会自动地加载并运行相应的程序,实现开机自启动功能。
快速启动目录并不是一个固定目录,每台计算机的快速启动目录都不相同(系统版本差异)。但是程序可以使用SHGetSpecialFolderPath函数获取Windows系统中快速启动目录的路径,快速启动目录的CSIDL标识值为CSIDL_STARTUP。
然后使用CopyFile函数,将想要自启动的程序复制到快速启动目录下即可。当然,为程序创建快捷方式,并把快捷方式放入到快速启动目录中,也同样可以达到开机自启动的效果。
第一步,新建C++空项目,项目名称为“Ziqidong02”。
第二步,新建源文件“main.cpp”,并添加如下代码。
#include
#include
#include
#include
#pragma comment(lib,"shell32.lib")
#define MAX_PATH 200
int main(int argc, char* argv[])
{
//标记变量
BOOL bRet = FALSE;
//定义路径
char szStartupPath[MAX_PATH] = { 0 };
char szDestFilePath[MAX_PATH] = { 0 };
//文件名称
LPCTSTR filename = "winmine.exe";
//获取快速启动目录的路径
bRet = ::SHGetSpecialFolderPath(NULL, szStartupPath, CSIDL_STARTUP, TRUE);
printf("szStartupPath=%s\n", szStartupPath);
if (bRet == FALSE)
{
printf("获取启动目录失败\n");
return 0;
}
//构造复制目的文件路径
::wsprintf(szDestFilePath, "%s\\%s", szStartupPath, filename);
printf("szDestFilePath=%s\n", szDestFilePath);
//复制文件到快速启动目录
bRet = ::CopyFile(filename, szDestFilePath, FALSE);
if (bRet == FALSE)
{
printf("复制文件失败\n");
return 0;
}
printf("文件成功复制至快速启动目录\n");
system("pause");
return 0;
}
第三步,运行程序,成功将该项目中的扫雷程序复制至快速启动目录。
如下图所示,下次开机即可自启动。
简单总结:
该方法比较简单,由于快速启动路径不是固定的,所以不能把快速启动目录的路径写死,应该通过调用SHGetSpecialFolderPath函数获取目录,对应的标识符为CSIDL_STARTUP,同时需要导入扩展包“shlobj.h”。注意,当我们在编写反病毒或安全软件时,直接枚举快速启动目录下的文件信息,可以获取开机启动项的信息。
Windows系统可以设置计划任务来执行一些定时任务,计划任务的触发条件为在用户登录时触发,执行启动指定路径程序的操作,从而实现开机自启动。对于用户来说,手动创建计划任务并不复杂,但是编程实现就比较复杂了。
在之前数据库操作的文章中,我介绍过通过计划任务备份数据或定时播放视频的功能。
在“控制面板” -> “系统和安全” -> “管理工具” -> “计划任务”中打开,显示如下图所示。
创建新的计划任务,设置自启动即可运行某个应用程序。
但上述方法是主动设置,而木马或病毒自启动通常是通过编写程序实现的。使用Windows Shell编程实现创建计划任务时,会涉及COM组件接口的调用。由于初学者对于COM组件知识接触较少,所以对这部分知识理解起来会有难度。
本书为方便读者理解,把整个程序的逻辑概括为3部分,它们分别是:
下面开始详细讲解,需要注意的是,编程实现创建计划任务要求具有管理员权限。
(1) 初始化操作
(2) 创建任务计划
(3) 删除计划任务
删除计划任务相对简单,其中ITaskFolder对象存储着已经注册成功的任务计划信息,程序只需要调用DeleteTask接口函数,并将任务计划的名称传入其中,就可以删除指定名称的计划任务。
下面仅给出核心代码,作者认为本文大家重点复现前两个操作,后面的技术先简单了解,到时候具体用到时再回过头来继续学习。
对于创建计划任务来说,关键点是设置任务的触发条件及执行操作。将触发条件设置为用户登录,执行操作设置为启动程序,这样程序就可以实现开机自启动功能。但是COM组件编程可能有些陌生,希望大家多实践、多写代码,慢慢习惯各类安全技术及木马、反病毒编写流程。
CMyTaskSchedule.h
//CMyTaskSchedule.h
#pragma once
#include
#pragma comment(lib, "taskschd.lib")
class CMyTaskSchedule
{
public:
CMyTaskSchedule();
~CMyTaskSchedule();
//************************************
// 函数名: CMyTaskSchedule::NewTask
// 返回类型: BOOL
// 功能: 创建计划任务
// 参数1: char * lpszTaskName 计划任务名
// 参数2: char * lpszProgramPath 计划任务路径
// 参数3: char * lpszParameters 计划任务参数
// 参数4: char * lpszAuthor 计划任务作者
//************************************
BOOL NewTask(char* lpszTaskName, char* lpszProgramPath, char* lpszParameters, char* lpszAuthor);
//************************************
// 函数名: CMyTaskSchedule::Delete
// 返回类型: BOOL
// 功能: 删除计划任务
// 参数1: char * lpszTaskName 计划任务名
//************************************
BOOL Delete(char* lpszTaskName);
private:
ITaskService *m_lpITS;
ITaskFolder *m_lpRootFolder;
};
CMyTaskSchedule.cpp
//CMyTaskSchedule.cpp
#include "pch.h"
#include "CMyTaskSchedule.h"
CMyTaskSchedule::CMyTaskSchedule()
{
m_lpITS = NULL;
m_lpRootFolder = NULL;
//初始化COM
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr))
{
MessageBox(NULL, L"初始化COM接口环境失败!", L"Error", MB_OK);
return;
}
//创建任务服务对象
hr = CoCreateInstance(CLSID_TaskScheduler, NULL,
CLSCTX_INPROC_SERVER, IID_ITaskService,
(LPVOID*)(&m_lpITS));
if (FAILED(hr))
{
MessageBox(NULL, L"创建任务服务对象失败!", L"Error", MB_OK);
return;
}
//连接到任务服务
hr = m_lpITS->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
if (FAILED(hr))
{
MessageBox(NULL, L"连接到任务服务失败!", L"Error", MB_OK);
return;
}
//获取根任务的指针
//获取Root Task Folder的指针,这个指针指向的是新注册的任务
hr = m_lpITS->GetFolder(_bstr_t("\\"), &m_lpRootFolder);
if (FAILED(hr))
{
MessageBox(NULL, L"获取根任务的指针失败!", L"Error", MB_OK);
return;
}
}
CMyTaskSchedule::~CMyTaskSchedule()
{
if (m_lpITS)
{
m_lpITS->Release();
}
if (m_lpRootFolder)
{
m_lpRootFolder->Release();
}
// 卸载COM
CoUninitialize();
}
//创建任务计划
BOOL CMyTaskSchedule::NewTask(char* lpszTaskName, char* lpszProgramPath, char* lpszParameters, char* lpszAuthor)
{
if (NULL == m_lpRootFolder)
{
return FALSE;
}
//如果存在相同的计划任务则删除
Delete(lpszTaskName);
//创建任务定义对象来创建任务
ITaskDefinition *pTaskDefinition = NULL;
HRESULT hr = m_lpITS->NewTask(0, &pTaskDefinition);
if (FAILED(hr))
{
MessageBox(NULL, L"创建任务失败!", L"Error", MB_OK);
return FALSE;
}
//设置注册信息
IRegistrationInfo *pRegInfo = NULL;
CComVariant variantAuthor(NULL);
variantAuthor = lpszAuthor;
hr = pTaskDefinition->get_RegistrationInfo(&pRegInfo);
if (FAILED(hr))
{
MessageBox(NULL, L"设置注册信息失败!", L"Error", MB_OK);
return FALSE;
}
// 设置作者信息
hr = pRegInfo->put_Author(variantAuthor.bstrVal);
pRegInfo->Release();
// 设置登录类型和运行权限
IPrincipal *pPrincipal = NULL;
hr = pTaskDefinition->get_Principal(&pPrincipal);
if (FAILED(hr))
{
MessageBox(NULL, L"设置登录类型和运行权限失败!", L"Error", MB_OK);
return FALSE;
}
// 设置登录类型
hr = pPrincipal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN);
// 设置运行权限
// 最高权限
hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
pPrincipal->Release();
// 设置其他信息
ITaskSettings *pSettting = NULL;
hr = pTaskDefinition->get_Settings(&pSettting);
if (FAILED(hr))
{
MessageBox(NULL, L"设置其他信息失败!", L"Error", MB_OK);
return FALSE;
}
// 设置其他信息
hr = pSettting->put_StopIfGoingOnBatteries(VARIANT_FALSE);
hr = pSettting->put_DisallowStartIfOnBatteries(VARIANT_FALSE);
hr = pSettting->put_AllowDemandStart(VARIANT_TRUE);
hr = pSettting->put_StartWhenAvailable(VARIANT_FALSE);
hr = pSettting->put_MultipleInstances(TASK_INSTANCES_PARALLEL);
pSettting->Release();
// 创建执行动作
IActionCollection *pActionCollect = NULL;
hr = pTaskDefinition->get_Actions(&pActionCollect);
if (FAILED(hr))
{
MessageBox(NULL, L"创建执行动作失败!", L"Error", MB_OK);
return FALSE;
}
IAction *pAction = NULL;
// 创建执行操作
hr = pActionCollect->Create(TASK_ACTION_EXEC, &pAction);
pActionCollect->Release();
// 设置执行程序路径和参数
CComVariant variantProgramPath(NULL);
CComVariant variantParameters(NULL);
IExecAction *pExecAction = NULL;
hr = pAction->QueryInterface(IID_IExecAction, (PVOID *)(&pExecAction));
if (FAILED(hr))
{
pAction->Release();
MessageBox(NULL, L"创建执行动作失败!", L"Error", MB_OK);
return FALSE;
}
pAction->Release();
// 设置程序路径和参数
variantProgramPath = lpszProgramPath;
variantParameters = lpszParameters;
pExecAction->put_Path(variantProgramPath.bstrVal);
pExecAction->put_Arguments(variantParameters.bstrVal);
pExecAction->Release();
// 设置触发器信息,包括用户登录时触发
ITriggerCollection *pTriggers = NULL;
hr = pTaskDefinition->get_Triggers(&pTriggers);
if (FAILED(hr))
{
MessageBox(NULL, L"设置触发器信息失败!", L"Error", MB_OK);
return FALSE;
}
// 创建触发器
ITrigger *pTrigger = NULL;
hr = pTriggers->Create(TASK_TRIGGER_LOGON, &pTrigger);
if (FAILED(hr))
{
MessageBox(NULL, L"创建触发器失败!", L"Error", MB_OK);
return FALSE;
}
// 注册任务计划
IRegisteredTask *pRegisteredTask = NULL;
CComVariant variantTaskName(NULL);
variantTaskName = lpszTaskName;
hr = m_lpRootFolder->RegisterTaskDefinition(variantTaskName.bstrVal,
pTaskDefinition,
TASK_CREATE_OR_UPDATE,
_variant_t(),
_variant_t(),
TASK_LOGON_INTERACTIVE_TOKEN,
_variant_t(""),
&pRegisteredTask);
if (FAILED(hr))
{
pTaskDefinition->Release();
MessageBox(NULL, L"注册任务计划失败!", L"Error", MB_OK);
return FALSE;
}
pTaskDefinition->Release();
pRegisteredTask->Release();
return TRUE;
}
//删除计划任务
BOOL CMyTaskSchedule::Delete(char* lpszTaskName)
{
if(NULL == m_lpRootFolder)
{
return FALSE;
}
CComVariant variantTaskName(NULL);
variantTaskName = lpszTaskName;
HRESULT hr = m_lpRootFolder->DeleteTask(variantTaskName.bstrVal, 0);
if (FAILED(hr))
{
return FALSE;
}
return TRUE;
}
注意,创建计划任务要求程序必须有管理员权限,而防御需要通过“计算机管理”查看系统中设置的计划任务,根据任务的触发器和操作信息来判断是否有异常的开机自启动项。
打开计算机上的任务管理器,可以发现许多系统服务进程在后台运行,而且大多数的系统服务进程都是随着系统启动而启动的,如svchost.exe进程。
系统服务运行在SESSION 0,由于系统服务的SESSION 0 隔离,阻断了系统服务额用户桌面进程之间进行交互和通信的桥梁。各个会话之间是相互独立的,在不同会话中运行的实体,相互之间不能发送Windows消息、共享UI元素,或者是在没有指定有有限访问全局名字空间(且提供正确的访问控制设置)的情况下共享核心对象。这也是为什么在系统服务中不能显示程序界面的原因,也不能用常规的方式创建有界面的进程。
系统进程自启动是通过创建系统服务并设置服务启动类型为自动启动来实现的,接下来我们进行相关介绍。
(1) OpenSCManager函数
建立了一个到服务控制管理器的连接,并打开指定的数据库。如果函数成功,则返回一个服务控制管理器数据库的句柄;否则返回NULL。
SC_HANDLE WINAPI OpenSCManager(
__in_opt LPCTSTR lpMachineName, //指定计算机名称
__in_opt LPCTSTR lpDatabaseName, //指定要打开服务控制管理数据库的名称
__in DWORD dwDesiredAccess //指定服务访问控制管理器的权限
);
(2) CreateService函数
建立一个服务对象,并将其添加到指定的服务控制管理器数据库中。如果函数成功,则返回该服务的句柄;否则返回NULL。
SC_HANDLE CreateService(
SC_HANDLE hSCManager, //服务控制管理器数据库的句柄
LPCTSTR lpServiceName, //要安装服务的名称
LPCTSTR lpDisplayName, //用户界面标识服务的显示名称
DWORD dwDesiredAccess, //对服务的访问
DWORD dwServiceType, //指定服务类型
DWORD dwStartType, //指定服务启动选项
DWORD dwErrorControl, //指定服务启动失败的严重程度
LPCTSTR lpBinaryPathName, //指定服务程序二进制文件的路径
LPCTSTR lpLoadOrderGroup, //指定顺序装入的服务组名
LPDWORD lpdwTagId, //标记变量
LPCTSTR lpDependencies, //指定启动该服务前必须先启动的服务或服务组
LPCTSTR lpServiceStartName, //该服务应运行的账户名称
LPCTSTR lpPassword //指定账户名的密码
);
其中,dwStartType共有五种启动类型。
(3) OpenService函数
打开一个已经存在的服务。如果函数成功,则返回该服务的句柄;否则返回NULL,可以通过GetLastError获取错误码。
SC_HANDLE WINAPI OpenService(
SC_HANDLE hSCManager, //指向SCM数据库句柄
LPCTSTR lpServiceName, //要打开服务的名称
DWORD dwDesiredAccess //指定服务权限
);
(4) StartService函数
启动服务。如果函数成功,则返回非零数值;否则返回0,可以通过GetLastError获取错误码。
SC_HANDLE WINAPI StartService(
SC_HANDLE hService, //OpenService或CreateService函数返回服务句柄
DWORD dwNumServiceArgs, //下一个形参lpServiceArgVectors字符串个数
LPCTSTR *lpServiceArgVectors //传给服务ServiceMain的参数
);
(5) StartServiceCtrlDispatcher函数
将服务进程的主线程连接到服务控制管理器,该线程将作为调用过程的服务控制分派器线程。如果函数成功,则返回非零数值;否则返回0,可以通过GetLastError获取错误码。
BOOL WINAPI StartServiceCtrlDispatcher(
_In_ const SERVICE_TABLE_ENTRY * lpServiceTable
);
在创建自启动系统服务前,我们先实现一个检查Windows服务运行状态的程序。在我们制作杀毒软件、反病毒软件过程中,通常都需要扫描相关信息,再进行深入的判断。该部分参考文献[7],推荐大家阅读原文。
第一步,创建工程“Ziqidong-get”。
第二步,添加如下代码。
#include
#include
#include
/* 检查Windows服务状态信息 */
int main(void)
{
TCHAR szSvcName[] = _T("HistorySvr");
SC_HANDLE schSCManager = NULL;
SC_HANDLE schService = NULL;
SERVICE_STATUS_PROCESS ssStatus;
DWORD dwOldCheckPoint = 0;
DWORD dwStartTickCount = 0;
DWORD dwWaitTime = 0;
DWORD dwBytesNeeded = 0;
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
szSvcName, // name of service
SERVICE_QUERY_STATUS |
SERVICE_ENUMERATE_DEPENDENTS); // full access
if (schService == NULL)
{
printf("OpenService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
}
// Check the status in case the service is not stopped.
if (!QueryServiceStatusEx(
schService, // handle to service
SC_STATUS_PROCESS_INFO, // information level
(LPBYTE)&ssStatus, // address of structure
sizeof(SERVICE_STATUS_PROCESS), // size of structure
&dwBytesNeeded)) // size needed if buffer is too small
{
printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
else
{
// Check if the service is already running. It would be possible
// to stop the service here, but for simplicity this example just returns.
printf("Service status: ");
switch (ssStatus.dwCurrentState)
{
case SERVICE_STOPPED:
case SERVICE_STOP_PENDING:
printf("Stop");
break;
case SERVICE_PAUSED:
case SERVICE_PAUSE_PENDING:
printf("Pause");
break;
case SERVICE_CONTINUE_PENDING:
case SERVICE_RUNNING:
case SERVICE_START_PENDING:
printf("Running");
break;
}
printf("获取结束\n");
}
system("pause");
return 0;
}
第三步,设置管理员权限运行该程序。
注意,OpenService函数打开“禁用”的服务时,提示1060错误。
许多的病毒和木马都将自己注册为系统服务,来实现自启动。有的注册新的服务,有的替换现有服务实现隐藏。添加系统服务是Windows木马一个普遍使用的技术,还是比较实用的。下面开始介绍创建自启动系统服务进程,主要分为两部分。
(1) 创建启动系统服务
(2) 系统服务程序编写
自启动服务程序不是普通程序,而是要求程序创建服务入口点函数,否则,不能创建系统服务。创建系统服务程序的流程如下。调用系统函数StartServiceCtrlDispatcher将程序主线程连接到服务控制管理程序,其中定义服务入口函数是ServiceMain。服务控制管理程序启动服务程序后,等待服务程序主函数调用StartServiceCtrlDispatcher函数。
第一步,尝试添加一个新的服务操作。
#include
#include
#include
#define SRV_NAME "SrvSample" //服务名称
#define SRV_INFO "添加一个系统服务程序实例" //服务说明
#define SRV_PATH "C:\\Users\\xiuzhang\\Desktop\\winmine.exe" //系统服务程序的路径
int main(int argc, char** argv)
{
SC_HANDLE scMgr = NULL;
SC_HANDLE service = NULL;
//打开SCM
scMgr = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
if (scMgr == NULL) {
printf("OpenSCManager Error\n");
return 0;
}
//创建服务
service = CreateService(scMgr,
SRV_NAME,
SRV_INFO,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
SRV_PATH,0, 0, 0, 0, 0);
if (service) {
printf("Create a new service successful!\n");
}
else {
printf("Failed to create a new service!\n");
}
//关闭句柄
CloseServiceHandle(scMgr);
CloseServiceHandle(service);
system("pause");
return 0;
}
360软件允许该创建。
显示结果如下图所示,成功创建,其代码核心是先打开服务管理器的句柄,然后调用API添加服务。
第二步,接着我们给出代码演示如何删除一个服务。
#include
#include
#include
#define SRV_NAME "SrvSample" //服务名称
#define SRV_INFO "添加一个系统服务程序实例" //服务说明
#define SRV_PATH "C:\\Users\\xiuzhang\\Desktop\\winmine.exe" //系统服务程序的路径
int main(int argc, char** argv)
{
SC_HANDLE scMgr;
SC_HANDLE service;
SERVICE_STATUS status;
//打开服务
scMgr = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
service = OpenService(scMgr, SRV_NAME, SERVICE_ALL_ACCESS | DELETE);
//查询服务状态
QueryServiceStatus(service, &status);
//如果服务未停止,先停止它
if (status.dwCurrentState != SERVICE_STOPPED) {
ControlService(service, SERVICE_CONTROL_STOP, &status);
Sleep(500);
}
//删除服务
BOOL bSuccess = DeleteService(service);
if (bSuccess)
printf("Delete service successful!\n");
else
printf("Failed to delete service!\n");
system("pause");
return 0;
}
第三步,给出一个服务程序的模板。一个服务程序必须有两个函数,一个是服务程序的主函数ServiceMain(),另一个是服务程序的派遣函数ServiceHandler(),它负责处理外部控制消息。
#include
#include
#include
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;
VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv);
VOID WINAPI ServiceHandler(DWORD fdwControl);
int main(int argc, char** argv)
{
//设置服务信息 ServiceMain服务入口函数
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceName = "SrvSample";
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;
//将服务进程的主线程连接到服务控制管理器
StartServiceCtrlDispatcher(ServiceTable);
system("pause");
return 0;
}
//服务控制处理函数
VOID WINAPI ServiceHandler(DWORD fdwControl)
{
switch (fdwControl)
{
case SERVICE_CONTROL_PAUSE:
ServiceStatus.dwCurrentState = SERVICE_PAUSED;
break;
case SERVICE_CONTROL_CONTINUE:
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
break;
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
SetServiceStatus(hStatus, &ServiceStatus);
return;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
break;
}
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
//服务主函数
VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv)
{
DWORD status = 0;
DWORD specificError = 0xfffffff;
ServiceStatus.dwServiceType = SERVICE_WIN32;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
hStatus = RegisterServiceCtrlHandler("SrvSample",
(LPHANDLER_FUNCTION)ServiceHandler);
if (hStatus == 0)
return;
// Handle error condition
status = GetLastError();
if (status != NO_ERROR)
{
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
ServiceStatus.dwWin32ExitCode = status;
ServiceStatus.dwServiceSpecificExitCode = specificError;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
// 初始化结束,报告运行状态
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
SetServiceStatus(hStatus, &ServiceStatus);
//在这里做其他的工作
printf("设置成功\n");
}
这是一个模板,要实现你自己的服务,在DoWork()中添加功能代码即可。
写到这里,这篇文章就介绍完毕,希望对您有所帮助,最后进行简单的总结下,个人更喜欢注册表和快速启动目录两个技术,推荐读者把这部分代码实现。后面计划任务我建议结合实际去做,更推荐手动设置定时任务(如数据库、爬虫每日执行),而系统服务比较高深,随着我们研究和理解深入,可能帮助更大。
注意,不要觉得前面两个技术代码实现容易就简单,很多木马、病毒、APT攻击都用到了它们,不要小瞧任何一个技术,只有把这些技术组合起来威胁更大。同样,作为反病毒或安全分析人员,我们需要了解各种技术,只有知道怎么攻击和原理才能更好地防守。
最后非常犹豫,我到底要不要分享一个完整的且具有强大攻防或远控程序呢?包括文件管理、shell提取、自启动、屏幕控制、录音控制、键盘记录、痕迹获取、U盘记录等功能。希望大家在评论区也回复下我,如果不忙,我可能在第二个系列“安全攻防进阶篇”中花20篇左右文章分享,因为这个系列100篇即将结束。感恩有你,感恩这一年你的陪伴,也希望安全路上我们能不断深入,成为技术牛人,加油~
学安全一年,认识了很多安全大佬和朋友,希望大家一起进步。这篇文章中如果存在一些不足,还请海涵。作者作为网络安全初学者的慢慢成长路吧!希望未来能更透彻撰写相关文章。同时非常感谢参考文献中的安全大佬们的文章分享,深知自己很菜,得努力前行。
(By:Eastmount 2020-08-15 星期一 晚上9点写于武汉 http://blog.csdn.net/eastmount/ )
参考文献:
[1] C# 系统应用之注册表使用详解 - Eastmount
[2] https://baike.baidu.com/item/RegOpenKeyEx/3716633
[3] https://baike.baidu.com/item/RegSetValueEx/9735249
[4] [数据库] Navicat for MySQL定时备份数据库及数据恢复 - Eastmount
[5] 通过计划任务实现开机自启动 - 自己的小白
[6] https://docs.microsoft.com/en-us/previous-versions/aa931257(v=msdn.10)
[7] C++ 检查Windows服务运行状态 - 我来乔23
[8] https://docs.microsoft.com/zh-cn/windows/win32/services/svccontrol-cpp?redirectedfrom=MSDN
[9] Windows黑客编程基础(一)——系统服务