第7章 分析恶意的windows程序

1. windows API

1.1类型和匈牙利表示法

windows API使用的变量名会使用前缀来说明它的类型,是一种命名规范


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

AutoRuns

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 
#include 
#include 
#include 
//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;
}

使用

tcp客户端

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来获取另一个进程中互斥量的句柄
恶意代码通常会创建一个互斥量,并试图用同一名称打开一个已经存在的互斥量,来确定是否有自身实例在运行


image.png

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 是用来与服务控制管理器和服务进行通信
        的命令行程序。
用法:
        sc  [command] [service name]  ...


         选项的格式为 "\\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   

4.6 组件对象模型

COM是一种接口标准,使用C-S模式,客户端为使用COM组件的程序,服务端为可利用的软件组件 即COM对象本身
一般客户端程序需要使用OleInitialize和CoInitializeEx

COM对象的使用

Com对象通过它们的全局唯一标识符GUID来访问 分为类型标识符(CLSID)和接口标识符(IID)
Navigate函数可打开 Internet Explorer 访问网址
CoCreateInstance 会返回一个结构体,其中含有函数地址


image.png

COM服务器恶意代码

实现一个COM服务器相关API

  • DllCanUnloadNow
  • DllGetClassObject
  • DllInstall
  • DllRegisterServer
  • DllUnregisterServer

4.7 异常处理

没看懂

5 内核与用户模式

操作系统和硬件驱动运行在内核模式,所有运行在内核模式的程序共享资源 和内存地址
运行在内核中的代码可以操纵用户空间的代码,用户空间的代码直接通过给定的接口来影响内核
大多数的反病毒软件和防火墙都运行在内核模式,用于监控系统上所有程序的运行,运行在内核模式的代码可以绕过安全程序
rootkit

6 原生API

原生API是底层api


image.png

微软不提供原生api的文档, 但是可以在这个网站查询
http://undocumented.ntinternals.net/
恶意代码调用原生API可以绕过检测

你可能感兴趣的:(第7章 分析恶意的windows程序)