1. windows API
1.1类型和匈牙利表示法
windows API使用的变量名会使用前缀来说明它的类型,是一种命名规范
1.2 句柄
在windows中表示一个打开的对象 如窗口、进程、模块、文件、等待
用于引用一个对象,不能对句柄进行数学操作
1.3 文件系统函数
CreateFile https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
ReadFile WriteFile
CreateFileMapping 将文件加载到内存中
MapViewOfFile 返回一个指向映射的基地址指针
1.4 特殊文件
共享文件
\servername\share
\?\servername\share \?表示禁用所有的字符串解析,并允许访问长文件名
通过名字空间访问的文件
WinObj可查看windows下的名字空间
\.\ 为前缀的为win32设备名字空间 如\.\PhysicalDisk1
\Device\PhysicalDisk1
\Device\PhysicalMemory 直接访问物理内存
备用数据流
允许数据添加到一个已经存在的NTFS文件上,只有访问流时才显示,可用于数据隐藏
normalFile.txt.Stream:$DATA 来命名
2 windows注册表
用于保存操作系统和程序的配置信息
恶意代码常用注册表进行长久驻留
- 根键 5个顶层节
- 子键 类似于子文件夹
- 键 类似于文件夹
- 值项 一个键值对
- 值或数据 存储在注册表项中的值或数据
2.1 根键
- HKEY_LOCAL_MACHINE(HKLM) 保存本地机器的全局设置
- HKEY_LOCAL_USER(HKCU) 保存当前用户特定的设置
- HKEY_CLASSES_ROOT 保存定义的类型信息
- HKEY_CONRRENT_CONFIG 保存当前硬件的配置
- HKEY_USERS 定义默认用户、当前用户、新用户的配置
比如
HKEY_LOCAL_MACHINE\SOFTWARE\Micsosoft\Windows\CurrentVersion\Run 包含开机启动项
2.2 Regedit
windows自带的注册表编辑器
2.3 自启动程序
使用Autoruns可查看windows启动时会自动启动的代码
下载地址 https://download.sysinternals.com/files/Autoruns.zip
2.4 常用注册表函数
- RegOpenKeyEx 打开一个注册表顶进行编辑或查询
- RegSetValueEx 设置注册表项值
- RegGetValue 获取注册表项的值
2.5 使用.reg文件的注册表脚本
.reg文件可用于修改注册表
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run]
"SecurityHealth"=hex(2):25,00,77,00,69,00,6e,00,64,00,69,00,72,00,25,00,5c,00,\
73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,53,00,65,00,63,00,75,\
00,72,00,69,00,74,00,79,00,48,00,65,00,61,00,6c,00,74,00,68,00,53,00,79,00,\
73,00,74,00,72,00,61,00,79,00,2e,00,65,00,78,00,65,00,00,00
"IgfxTray"="\"C:\\Windows\\system32\\igfxtray.exe\""
"HotKeysCmds"="\"C:\\Windows\\system32\\hkcmd.exe\""
"Persistence"="\"C:\\Windows\\system32\\igfxpers.exe\""
3 网络API
3.1 伯克利兼容套接字
windows winsock套接字 由ws2_32.dll提供
服务器端
1、调用socket打开一个socket句柄
2、调用bind来绑定socket句柄到一个网口的某个端口
3、调用listen来设置(启用)监听
4、调用accept来等待客户端的连接
客户端
1、先是使用socket函数产生一个打开的socket文件描述符。
2、使用connect函数去连接服务端
3、使用read/recv等读文件函数从服务端接收数据,使用write/send等写文件的函数向服务端发送数据
程序的大致框架
1、初始化
/加载Winsock DLL/
WSADATA wsd;
if (WSAStartup(MAKEWORD(2 , 2) , &wsd) != 0) {
printf("Winsock 初始化失败!\n");
return 1;
}
2、socket相关函数调用
socket(...)
bind(...)
listen(...)
connect(...)
accept(...)
send/sendto
recv/recvfrom
3、清理
WSACleanup();
可参考https://www.cnblogs.com/oloroso/p/4613296.html
一个简单的tcp_client的例子
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//tcp socket客户端
//链接ws2_32.lib这个库
#pragma comment(lib , "ws2_32.lib")
#define BUFSIZE 4096 /*缓冲区大小*/
int main(int argc , char *argv[])
{
WSADATA wsd;
SOCKET sClient;
char* Buffer[BUFSIZE];
int ret;
struct sockaddr_in server;
unsigned short port;
struct hostent *host = NULL;
/*加载Winsock DLL*/
if (WSAStartup(MAKEWORD(2 , 2) , &wsd) != 0) {
printf("Winsock 初始化失败!\n");
return 1;
}
/*创建Socket*/
sClient = socket(AF_INET , SOCK_STREAM , IPPROTO_TCP);
if (sClient == INVALID_SOCKET) {
printf("socket() 失败: %d\n" , WSAGetLastError());
return 1;
}
/*指定服务器地址*/
server.sin_family = AF_INET;
port = atoi(argv[2]);
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr(argv[1]);
if (server.sin_addr.s_addr == INADDR_NONE) {
host = gethostbyname(argv[1]); //输入的地址可能是域名等
if (host == NULL) {
printf("无法解析服务端地址: %s\n" , argv[1]);
return 1;
}
CopyMemory(&server.sin_addr ,
host->h_addr_list[0] ,
host->h_length);
}
/*与服务器建立连接*/
if (connect(sClient , (struct sockaddr*)&server ,
sizeof(server)) == SOCKET_ERROR) {
printf("connect() 失败: %d\n" , WSAGetLastError());
return 1;
}
/*发送、接收消息*/
for (;;) {
//从标准输入读取要发送的数据
//gets_s(Buffer,BUFSIZE);
gets(Buffer);
strcat(Buffer,"\r\n\r\n");
//向服务器发送消息
ret = send(sClient , Buffer , strlen(Buffer) , 0);
if (ret == 0) {
break;
}
else if (ret == SOCKET_ERROR) {
printf("send() 失败: %d\n" , WSAGetLastError());
break;
}
printf("Send %d bytes\n" , ret);
//接收从服务器来的消息
ret = recv(sClient , Buffer , BUFSIZE , 0);
if (ret == 0) {
break;
}
else if (ret == SOCKET_ERROR) {
printf("recv() 失败: %d\n" , WSAGetLastError());
break;
}
Buffer[ret] = '\0';
printf("Recv %d bytes:\n\t%s\n" , ret , Buffer);
}
//用完了,关闭socket句柄(文件描述符)
closesocket(sClient);
WSACleanup(); //清理
return 0;
}
使用
3.3 WinINet API
更高一级的网络API 实现了HTTP和ftp协议
- InternetOpen
- InternetOpenUrl
- InternetReadFile
更多可参考 https://www.cnblogs.com/fuchongjundream/p/3853716.html
1、普通 WinInet 处理函数
⊙ InetrnetOpen 初始化 WinInet.dll
⊙ InternetOpenUrl 打开 Url,读取数据
⊙ InternetAttemptConnect 尝试建立到 Internet 的连接
⊙ InternetConnect 建立 Internet 的连接
⊙ InternetCheckConnection 检查 Internet 的连接是否能够建立
⊙ InternetSetOption 设置一个 Internet 选项
⊙ InternetSetStausCallback 安装一个回调函数,供 API 函数调用
⊙ InternetQueryOption 查询在一个指定句柄上的 Internet 选项
⊙ InternetQueryDataAvailable 查询可用数据的数量
⊙ InternetReadFile(Ex) 从一个打开的句柄读取数据
⊙ InternetFindNextFile 继续文件搜寻
⊙ InetrnetSetFilePointer 为 InternetReadFile 设置一个文件位置
⊙ InternetWriteFile 将数据写到一个打开的 Internet 文件
⊙ InternetLockRequestFile 允许用户为正在使用的文件加锁
⊙ InternetUnlockRequestFile 解锁被锁定的文件
⊙ InternetTimeFromSystemTime 根据指定的 RFC 格式格式化日期和时间
⊙ InternetTimeToSystemTime 将一个 HTTP 时间/日期字串格式化为 SystemTime 结构对象
⊙ InternetConfirmZoneCrossing 检查在安全 URL 和非安全 URL 间的变化
⊙ InternetCloseHandle 关闭一个单一的 Internet 句柄
⊙ InternetErrorDlg 显示错误信息对话框
⊙ InternetGetLastResponesInfo 获取最近发送的 API函数的错误
2、HTTP 处理函数
⊙ HttpOpenRequest 打开一个 HTTP 请求的句柄
⊙ HttpSendRequert(Ex) 向 HTTP 服务器发送指定的请求
⊙ HttpQueryInfo 查询有关一次 HTTP 请求的信息
⊙ HttpEndRequest 结束一个 HTTP 请求
⊙ HttpAddRequestHeaders 添加一个或多个 HTTP 请求报头到 HTTP请求句柄
3、FTP 处理函数
⊙ FtpCreateDirectory 在 Ftp 服务器新建一个目录
⊙ FtpDelectFile 删除存储在 Ftp 服务器上的文件
⊙ FtpFindFirstFile 查找给定 Ftp 会话中的指定目录
⊙ FtpGetCurrentDirectory 为指定 Ftp 会话获取当前目录
⊙ FtpGetFile 从 Ftp 服务器下载文件
⊙ FtpOpenFile 访问一个远程文件以对其进行读写
⊙ FtpPutFile 向 Ftp 服务器上传文件
⊙ FtpRemoveDirectory 在 Ftp 服务器删除指定的文件
⊙ FtpRenameFile 为 Ftp 服务器上的指定文件改名
⊙ FtpSetCurrentDirectory 更改在 Ftp 服务器上正在使用的目录
4 跟踪恶意代码的运行
4.1 DLL
dll用于在多个windows程序间共享代码 在内存中可以被不同的程序共享使用
恶意代码如何使用dll
- 保存恶意代码
- 通过使用windows dll
- 使用第三方的dll
4.2 进程
进程之间共享系统资源互不干扰
进程使用的的逻辑地址可相同,但是映射的物理地址不同
创建新进程
- CreateProcess https://docs.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
4.3 线程
一个进程可包含多个线程,这些线程共享内存空间,但是第一个拥有自己的处理器、寄存器和栈
线程上下文
操作系统在切换线程时,会将CPU中的所有值存储在线程上下文这个结构体中。轮到该线程时,会重启加载该线程上下文,恢复寄存器的值
创建一个线程
- CreateThread
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // NULL
SIZE_T dwStackSize,//0
LPTHREAD_START_ROUTINE lpStartAddress,//线程函数的地址
__drv_aliasesMem LPVOID lpParameter,//线程函数的参数
DWORD dwCreationFlags,//启动方式 0
LPDWORD lpThreadId//用来接收线程扫描符
);//返回线程的handle
lpStartAddress 原型为
DWORD WINAPI ThreadProc(
_In_ LPVOID lpParameter
);
如何创建线程可参数MSDN上的例子 https://docs.microsoft.com/zh-cn/windows/win32/procthread/creating-threads?redirectedfrom=MSDN
恶意代码的用法
- 通过CreateThead加载新的恶意代码库到进程中
- 为输入和输出创建出个线程 一个读取命令,一个返回结果
windows系统中还有纤程,纤程与线程类似,但是被一个线程管理,而不是操作系统,纤程共享单一的线程上下文。
4.4 使用互斥量在进程间协作
互斥量为全局对象,用于协调多个进程和线程,用于控制资源的访问
WaitForSingleObject获取互斥量
ReleaseMutex释放互斥量
可以通过CreateMutex函数创建互斥量,通过OpenMutex来获取另一个进程中互斥量的句柄
恶意代码通常会创建一个互斥量,并试图用同一名称打开一个已经存在的互斥量,来确定是否有自身实例在运行
4.5 服务
windows程序允许使用服务来使程序后台运行,代码被windows服务管理器调度和运行
可用于恶意代码的长久驻留 可获取 system账户权限 比较隐蔽
相关API
- OpenSCManager 打开服务管理器
- CreateService 创建服务
- StartService 启动服务
服务类型
- WIN32_SHARE_PROCESS 将服务的代码放在一个dll中,在一个共享的进程中组合不同的服务
- WIN32_OWN_PROCESS 在一个.exe中保存代码,作为一个进程来运行
- KERNEL_DRIVER 用来加载代码到内核
本地服务被保存在注册表中,每个服务在HKLM\SYSTEM\CurrentControlSet\Services下有一个字键
sc命令可用于添加、删除、启动、停止、查询服务
描述:
SC 是用来与服务控制管理器和服务进行通信
的命令行程序。
用法:
sc <server> [command] [service name] <option1> <option2>...
<server> 选项的格式为 "\\ServerName"
可通过键入以下命令获取有关命令的更多帮助: "sc [command]"
命令:
query-----------查询服务的状态,
或枚举服务类型的状态。
queryex---------查询服务的扩展状态,
或枚举服务类型的状态。
start-----------启动服务。
pause-----------向服务发送 PAUSE 控制请求。
interrogate-----向服务发送 INTERROGATE 控制请求。
continue--------向服务发送 CONTINUE 控制请求。
stop------------向服务发送 STOP 请求。
config----------更改服务的配置(永久)。
description-----更改服务的描述。
failure---------更改失败时服务执行的操作。
failureflag-----更改服务的失败操作标志。
sidtype---------更改服务的服务 SID 类型。
privs-----------更改服务的所需特权。
managedaccount--更改服务以将服务帐户密码
标记为由 LSA 管理。
qc--------------查询服务的配置信息。
qdescription----查询服务的描述。
qfailure--------查询失败时服务执行的操作。
qfailureflag----查询服务的失败操作标志。
qsidtype--------查询服务的服务 SID 类型。
qprivs----------查询服务的所需特权。
qtriggerinfo----查询服务的触发器参数。
qpreferrednode--查询服务的首选 NUMA 节点。
qmanagedaccount-查询服务是否将帐户
与 LSA 管理的密码结合使用。
qprotection-----查询服务的进程保护级别。
quserservice----查询用户服务模板的本地实例。
delete ----------(从注册表中)删除服务。
create----------创建服务(并将其添加到注册表中)。
control---------向服务发送控制。
sdshow----------显示服务的安全描述符。
sdset-----------设置服务的安全描述符。
showsid---------显示与任意名称对应的服务 SID 字符串。
triggerinfo-----配置服务的触发器参数。
preferrednode---设置服务的首选 NUMA 节点。
GetDisplayName--获取服务的 DisplayName。
GetKeyName------获取服务的 ServiceKeyName。
EnumDepend------枚举服务依赖关系。
以下命令不需要服务名称:
sc <server> <command> <option>
boot------------(ok | bad)指示是否应将上一次启动另存为
最近一次已知的正确启动配置
Lock------------锁定服务数据库
QueryLock-------查询 SCManager 数据库的 LockStatus
示例:
sc start MyService
QUERY 和 QUERYEX 选项:
如果查询命令带服务名称,将返回
该服务的状态。其他选项不适合这种
情况。如果查询命令不带参数或
带下列选项之一,将枚举此服务。
type= 要枚举的服务的类型(driver, service, userservice, all)
(默认 = service)
state= 要枚举的服务的状态 (inactive, all)
(默认 = active)
bufsize= 枚举缓冲区的大小(以字节计)
(默认 = 4096)
ri= 开始枚举的恢复索引号
(默认 = 0)
group= 要枚举的服务组
(默认 = all groups)
语法示例
sc query - 枚举活动服务和驱动程序的状态
sc query eventlog - 显示 eventlog 服务的状态
sc queryex eventlog - 显示 eventlog 服务的扩展状态
sc query type= driver - 仅枚举活动驱动程序
sc query type= service - 仅枚举 Win32 服务
sc query state= all - 枚举所有服务和驱动程序
sc query bufsize= 50 - 枚举缓冲区为 50 字节
sc query ri= 14 - 枚举时恢复索引 = 14
sc queryex group= "" - 枚举不在组内的活动服务
sc query type= interact - 枚举所有不活动服务
sc query type= driver group= NDIS - 枚举所有 NDIS 驱动程序
4.6 组件对象模型
COM是一种接口标准,使用C-S模式,客户端为使用COM组件的程序,服务端为可利用的软件组件 即COM对象本身
一般客户端程序需要使用OleInitialize和CoInitializeEx
COM对象的使用
Com对象通过它们的全局唯一标识符GUID来访问 分为类型标识符(CLSID)和接口标识符(IID)
Navigate函数可打开 Internet Explorer 访问网址
CoCreateInstance 会返回一个结构体,其中含有函数地址
COM服务器恶意代码
实现一个COM服务器相关API
- DllCanUnloadNow
- DllGetClassObject
- DllInstall
- DllRegisterServer
- DllUnregisterServer
4.7 异常处理
没看懂
5 内核与用户模式
操作系统和硬件驱动运行在内核模式,所有运行在内核模式的程序共享资源 和内存地址
运行在内核中的代码可以操纵用户空间的代码,用户空间的代码直接通过给定的接口来影响内核
大多数的反病毒软件和防火墙都运行在内核模式,用于监控系统上所有程序的运行,运行在内核模式的代码可以绕过安全程序
rootkit
6 原生API
原生API是底层api
微软不提供原生api的文档, 但是可以在这个网站查询
http://undocumented.ntinternals.net/
恶意代码调用原生API可以绕过检测