第7章 分析恶意的windows程序

1. windows API

1.1类型和匈牙利表示法

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


windows API的常见类型

1.2 句柄

在windows中表示一个打开的对象 如窗口、进程、模块、文件、等待
用于引用一个对象,不能对句柄进行数学操作

1.3 文件系统函数

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 <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;
}

使用

tcp客户端

3.3 WinINet API

更高一级的网络API 实现了HTTP和ftp协议

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 进程

进程之间共享系统资源互不干扰
进程使用的的逻辑地址可相同,但是映射的物理地址不同

创建新进程

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 <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 会返回一个结构体,其中含有函数地址


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可以绕过检测

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容